@surlymochan/evalid 0.0.0-stage → 0.3.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.
Files changed (192) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/LICENSE +21 -0
  3. package/README.md +315 -2
  4. package/RELEASE_NOTES.md +102 -0
  5. package/atoms/registry.json +758 -0
  6. package/bin/xflow.js +7208 -0
  7. package/docs/adoption/README.md +128 -0
  8. package/docs/adoption/openflow-release-hardening.md +61 -0
  9. package/docs/benchmark-openflow-vs-gstack-superpowers-openspec-2026-05-25.md +81 -0
  10. package/docs/compatibility.md +52 -0
  11. package/docs/competitive-benchmark.md +108 -0
  12. package/docs/corps-operator-guide.md +75 -0
  13. package/docs/demo-proof.md +126 -0
  14. package/docs/examples-gallery.md +115 -0
  15. package/docs/fixtures/tracker-item.json +7 -0
  16. package/docs/goal-vs-codex.md +129 -0
  17. package/docs/gstack-comparison.md +88 -0
  18. package/docs/install-upgrade.md +133 -0
  19. package/docs/integrations.md +49 -0
  20. package/docs/launch-demo.md +123 -0
  21. package/docs/launch-dossier.md +180 -0
  22. package/docs/methodology.md +149 -0
  23. package/docs/npm-publish-handoff.md +83 -0
  24. package/docs/openspec-migration.md +106 -0
  25. package/docs/public-benchmark.md +68 -0
  26. package/docs/public-release.md +145 -0
  27. package/docs/quality-assessment.md +139 -0
  28. package/docs/quickstart.md +156 -0
  29. package/docs/reviewer-guide.md +43 -0
  30. package/docs/spec-kit-benchmark.md +73 -0
  31. package/docs/super-assistant-comparison.md +106 -0
  32. package/docs/superpowers-comparison.md +82 -0
  33. package/docs/team-adoption.md +123 -0
  34. package/docs/tooling-matrix.md +91 -0
  35. package/docs/walkthrough.md +76 -0
  36. package/docs/workflow-manual.md +1125 -0
  37. package/package.json +83 -4
  38. package/prompts/pencil-draft.md +46 -0
  39. package/prompts/pencil-refine.md +46 -0
  40. package/prompts/phase-artifact.md +24 -0
  41. package/prompts/phase-execution.md +32 -0
  42. package/schemas/competitor-reconstruction-contract.schema.json +91 -0
  43. package/schemas/llm-design-review-aggregate.schema.json +83 -0
  44. package/schemas/visual-benchmark.schema.json +978 -0
  45. package/schemas/workflow.schema.json +100 -0
  46. package/scripts/run-tests.mjs +23 -0
  47. package/scripts/takein_digest.py +139 -0
  48. package/specs/README.md +118 -0
  49. package/specs/architecture.md +36 -0
  50. package/specs/product.md +33 -0
  51. package/specs/workflow.md +349 -0
  52. package/src/agent_team/atoms/codex-app-bridge.js +8 -0
  53. package/src/agent_team/atoms/design/competitor-reconstruction-review.js +44 -0
  54. package/src/agent_team/atoms/design/contract-freeze.js +8 -0
  55. package/src/agent_team/atoms/design/generation-contract.js +442 -0
  56. package/src/agent_team/atoms/design/reconstruction-pack.js +246 -0
  57. package/src/agent_team/atoms/design/reference-surface-lock.js +49 -0
  58. package/src/agent_team/atoms/patch-challenge.js +19 -0
  59. package/src/agent_team/atoms/pencil/accept.js +9 -0
  60. package/src/agent_team/atoms/pencil/draft.js +365 -0
  61. package/src/agent_team/atoms/pencil/refine.js +229 -0
  62. package/src/agent_team/atoms/phase-run-dispatch.js +8 -0
  63. package/src/agent_team/atoms/team-run.js +380 -0
  64. package/src/agent_team/atoms/visual/aesthetic-review.js +231 -0
  65. package/src/agent_team/atoms/visual/benchmark-repair-plan.js +291 -0
  66. package/src/agent_team/atoms/visual/benchmark.js +1652 -0
  67. package/src/agent_team/atoms/visual/design-selection.js +23 -0
  68. package/src/agent_team/atoms/visual/direction-synthesis.js +124 -0
  69. package/src/agent_team/atoms/visual/dom-rect-export.js +129 -0
  70. package/src/agent_team/atoms/visual/image-diff.js +186 -0
  71. package/src/agent_team/atoms/visual/layout-competition.js +8 -0
  72. package/src/agent_team/atoms/visual/page-evidence-capture.js +806 -0
  73. package/src/agent_team/atoms/visual/playwright-runtime.js +8 -0
  74. package/src/agent_team/atoms/visual/review-aggregate.js +87 -0
  75. package/src/agent_team/atoms/visual/review.js +77 -0
  76. package/src/agent_team/atoms/visual/visual-report.js +197 -0
  77. package/src/agent_team/atoms/visual/visual-token-export.js +417 -0
  78. package/src/agent_team/common.js +311 -0
  79. package/src/core/atoms/agent-invoke.js +684 -0
  80. package/src/core/atoms/event/ingest.js +30 -0
  81. package/src/core/atoms/experience/digest.js +55 -0
  82. package/src/core/atoms/gate/artifact-verify.js +83 -0
  83. package/src/core/atoms/gate/competitor-contract-validate.js +119 -0
  84. package/src/core/atoms/gate/evidence-collect.js +64 -0
  85. package/src/core/atoms/gate/payload-from-phase-run.js +78 -0
  86. package/src/core/atoms/gate/visual-benchmark-validate.js +183 -0
  87. package/src/core/atoms/gate/workbench.js +79 -0
  88. package/src/core/atoms/intake/classify.js +15 -0
  89. package/src/core/atoms/intake/design-skill-select.js +16 -0
  90. package/src/core/atoms/intake/preview-or-refine.js +19 -0
  91. package/src/core/atoms/intake/recommend.js +20 -0
  92. package/src/core/atoms/intake/research-same-category.js +19 -0
  93. package/src/core/atoms/intervention/create.js +30 -0
  94. package/src/core/atoms/memory/manage.js +27 -0
  95. package/src/core/atoms/memory/mount.js +13 -0
  96. package/src/core/atoms/mission/create.js +19 -0
  97. package/src/core/atoms/mission/list.js +14 -0
  98. package/src/core/atoms/mission/show.js +26 -0
  99. package/src/core/atoms/notify/checkpoint.js +46 -0
  100. package/src/core/atoms/phase-run/complete.js +42 -0
  101. package/src/core/atoms/phase-run/fail.js +40 -0
  102. package/src/core/atoms/phase-run/persist.js +39 -0
  103. package/src/core/atoms/phase-run/start.js +30 -0
  104. package/src/core/atoms/plugin/run.js +76 -0
  105. package/src/core/atoms/policy/gate.js +90 -0
  106. package/src/core/atoms/policy/resolve.js +11 -0
  107. package/src/core/atoms/storage/sync.js +57 -0
  108. package/src/core/atoms/workflow/advance.js +25 -0
  109. package/src/core/atoms/workflow/guard.js +127 -0
  110. package/src/core/atoms/workflow/state-load.js +37 -0
  111. package/src/core/bindings.js +37 -0
  112. package/src/core/change-artifacts.js +134 -0
  113. package/src/core/competitive-score.js +265 -0
  114. package/src/core/concurrency-policy.js +35 -0
  115. package/src/core/execution-log.js +108 -0
  116. package/src/core/intake-recommendation.js +134 -0
  117. package/src/core/memory-state.js +62 -0
  118. package/src/core/mission-state.js +780 -0
  119. package/src/core/pending-gates.js +66 -0
  120. package/src/core/phase-cost.js +116 -0
  121. package/src/core/phase-run-read.js +27 -0
  122. package/src/core/policy-overlay.js +180 -0
  123. package/src/core/state-store.js +201 -0
  124. package/src/core/workflow-executor.js +998 -0
  125. package/src/core/workflow-integrity.js +111 -0
  126. package/src/core/workflow-loader.js +163 -0
  127. package/src/core/workflow-rerun.js +84 -0
  128. package/src/core/workflow-state.js +119 -0
  129. package/src/server.js +1307 -0
  130. package/workflows/corps.yaml +479 -0
  131. package/workflows/yolo.yaml +233 -0
  132. package/xflow/README.md +76 -0
  133. package/xflow/aha/SKILL.md +82 -0
  134. package/xflow/aha/scripts/append_aha.py +50 -0
  135. package/xflow/archive/scripts/archive_change.py +338 -0
  136. package/xflow/archive/templates/merge-design.md +54 -0
  137. package/xflow/archive/templates/merge-workflow.md +26 -0
  138. package/xflow/atoms/__init__.py +6 -0
  139. package/xflow/atoms/a1_issue_create_or_patch.py +70 -0
  140. package/xflow/atoms/a2_branch_create_and_link.py +42 -0
  141. package/xflow/atoms/a3_repo_doctor.py +11 -0
  142. package/xflow/atoms/a4_project_set_status.py +11 -0
  143. package/xflow/atoms/a5_archive_commit_push_close.py +11 -0
  144. package/xflow/atoms/a6_pr_create.py +72 -0
  145. package/xflow/atoms/b1_change_scaffold.py +11 -0
  146. package/xflow/atoms/b2_status_read.py +16 -0
  147. package/xflow/atoms/b2b_status_write.py +20 -0
  148. package/xflow/atoms/b3_status_transition.py +27 -0
  149. package/xflow/atoms/b4_handoff_scaffold_or_refresh.py +11 -0
  150. package/xflow/atoms/b5_aha_append.py +11 -0
  151. package/xflow/atoms/b6_takein_digest.py +11 -0
  152. package/xflow/atoms/b6b_lookback_digest.py +11 -0
  153. package/xflow/atoms/b7_change_mission_bind.py +43 -0
  154. package/xflow/atoms/c1_mem.py +54 -0
  155. package/xflow/atoms/e6_gate_local_precheck.py +23 -0
  156. package/xflow/atoms/h1_design_lite_gate.py +74 -0
  157. package/xflow/atoms/i6a_tdd_run.py +104 -0
  158. package/xflow/atoms/i6b_tdd_proof_validate.py +165 -0
  159. package/xflow/atoms/i6c_tdd_quality_review.py +357 -0
  160. package/xflow/atoms/j1_tests_run.py +129 -0
  161. package/xflow/atoms/j4a_spec_consistency_structural.py +105 -0
  162. package/xflow/atoms/j4b_spec_delta_review.py +316 -0
  163. package/xflow/atoms/j4c_openspec_migration_map.py +121 -0
  164. package/xflow/atoms/j4d_spec_clarify_gate.py +373 -0
  165. package/xflow/atoms/j6_cr_review.py +443 -0
  166. package/xflow/atoms/k1_artifacts_complete_check.py +39 -0
  167. package/xflow/atoms/k2_merge_snippets_apply.py +11 -0
  168. package/xflow/atoms/k3_handoff_refresh.py +14 -0
  169. package/xflow/atoms/k4_mem_lesson_persist.py +65 -0
  170. package/xflow/atoms/k5_archive_publish.py +21 -0
  171. package/xflow/atoms/k6_aha_merge.py +42 -0
  172. package/xflow/atoms/tdd_lib.py +747 -0
  173. package/xflow/corps/SKILL.md +109 -0
  174. package/xflow/exec/scripts/set_issue_in_progress.py +159 -0
  175. package/xflow/goal/SKILL.md +150 -0
  176. package/xflow/handoff/SKILL.md +95 -0
  177. package/xflow/handoff/scripts/scaffold_handoff.py +304 -0
  178. package/xflow/lookback/SKILL.md +66 -0
  179. package/xflow/lookback/scripts/lookback_digest.py +46 -0
  180. package/xflow/openissue/scripts/open_issue_flow.py +438 -0
  181. package/xflow/plan/SKILL.md +38 -0
  182. package/xflow/ralph/SKILL.md +123 -0
  183. package/xflow/scripts/check_installed_xflow_skill_sync.sh +44 -0
  184. package/xflow/scripts/common.py +480 -0
  185. package/xflow/scripts/doctor.py +278 -0
  186. package/xflow/scripts/sync_installed_xflow_skill.sh +134 -0
  187. package/xflow/takein/SKILL.md +81 -0
  188. package/xflow/takein/scripts/takein_digest.py +88 -0
  189. package/xflow/yolo/SKILL.md +66 -0
  190. package/xflow/yolo/scripts/scaffold_specs_change.py +673 -0
  191. package/xflow/yolo/scripts/yolo_gate.py +312 -0
  192. package/xflow/yolo/templates/DESIGN.md +179 -0
@@ -0,0 +1,417 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+
4
+ function pickNodes(snapshot) {
5
+ if (Array.isArray(snapshot)) return snapshot;
6
+ const collected = [];
7
+ if (Array.isArray(snapshot?.nodes)) collected.push(...snapshot.nodes);
8
+ if (Array.isArray(snapshot?.states)) {
9
+ for (const state of snapshot.states) {
10
+ if (Array.isArray(state?.nodes)) collected.push(...state.nodes);
11
+ }
12
+ }
13
+ if (collected.length > 0) return collected;
14
+ if (Array.isArray(snapshot?.dom_snapshot?.nodes)) return snapshot.dom_snapshot.nodes;
15
+ if (Array.isArray(snapshot?.snapshot?.nodes)) return snapshot.snapshot.nodes;
16
+ return [];
17
+ }
18
+
19
+ function toNumber(value) {
20
+ if (typeof value === 'number') return Number.isFinite(value) ? value : null;
21
+ if (typeof value !== 'string') return null;
22
+ const parsed = Number.parseFloat(value);
23
+ return Number.isFinite(parsed) ? parsed : null;
24
+ }
25
+
26
+ function normalizeColor(value) {
27
+ if (typeof value !== 'string') return null;
28
+ const trimmed = value.trim().toLowerCase();
29
+ return trimmed || null;
30
+ }
31
+
32
+ function toDurationMs(value) {
33
+ if (typeof value !== 'string') return [];
34
+ return value
35
+ .split(',')
36
+ .map((entry) => entry.trim().toLowerCase())
37
+ .flatMap((entry) => {
38
+ if (!entry || entry === '0s' || entry === '0ms') return [0];
39
+ if (entry.endsWith('ms')) {
40
+ const parsed = Number.parseFloat(entry.slice(0, -2));
41
+ return Number.isFinite(parsed) ? [parsed] : [];
42
+ }
43
+ if (entry.endsWith('s')) {
44
+ const parsed = Number.parseFloat(entry.slice(0, -1));
45
+ return Number.isFinite(parsed) ? [parsed * 1000] : [];
46
+ }
47
+ return [];
48
+ });
49
+ }
50
+
51
+ function toStringList(value) {
52
+ if (typeof value !== 'string') return [];
53
+ const entries = [];
54
+ let current = '';
55
+ let depth = 0;
56
+ for (const char of value) {
57
+ if (char === '(') depth += 1;
58
+ if (char === ')') depth = Math.max(0, depth - 1);
59
+ if (char === ',' && depth === 0) {
60
+ const normalized = current.trim().toLowerCase();
61
+ if (normalized) entries.push(normalized);
62
+ current = '';
63
+ continue;
64
+ }
65
+ current += char;
66
+ }
67
+ const normalized = current.trim().toLowerCase();
68
+ if (normalized) entries.push(normalized);
69
+ return entries;
70
+ }
71
+
72
+ function pushUnique(list, value) {
73
+ if (value === null || value === undefined) return;
74
+ if (!list.includes(value)) list.push(value);
75
+ }
76
+
77
+ function summarizeFamilyStyles(entries = []) {
78
+ const textSizes = [];
79
+ const lineHeights = [];
80
+ const radiusValues = [];
81
+ const borderWidths = [];
82
+ for (const entry of entries) {
83
+ if (!entry || typeof entry !== 'object') continue;
84
+ pushUnique(textSizes, toNumber(entry.fontSize));
85
+ pushUnique(lineHeights, toNumber(entry.lineHeight));
86
+ pushUnique(radiusValues, toNumber(entry.borderRadius));
87
+ pushUnique(borderWidths, toNumber(entry.borderWidth));
88
+ }
89
+ return {
90
+ text_sizes: textSizes.sort((a, b) => a - b),
91
+ line_heights: lineHeights.sort((a, b) => a - b),
92
+ radius_values: radiusValues.sort((a, b) => a - b),
93
+ border_widths: borderWidths.sort((a, b) => a - b),
94
+ };
95
+ }
96
+
97
+ function classifyBorderWeight(value) {
98
+ if (!Number.isFinite(value) || value <= 0) return null;
99
+ if (value <= 1) return 'hairline';
100
+ if (value <= 2) return 'regular';
101
+ return 'strong';
102
+ }
103
+
104
+ function classifyShadowStrength(signature) {
105
+ if (typeof signature !== 'string' || !signature.trim()) return null;
106
+ const numbers = signature.match(/-?\d*\.?\d+/g)?.map(Number.parseFloat).filter(Number.isFinite) || [];
107
+ const alphaMatch = signature.match(/rgba?\([^)]*,\s*([0-9.]+)\s*\)$/);
108
+ const alpha = alphaMatch ? Number.parseFloat(alphaMatch[1]) : 1;
109
+ const blur = numbers.length >= 3 ? Math.abs(numbers[2]) : 0;
110
+ const spread = numbers.length >= 4 ? Math.abs(numbers[3]) : 0;
111
+ const score = (blur + spread) * (Number.isFinite(alpha) ? alpha : 1);
112
+ if (score <= 4) return 'subtle';
113
+ if (score <= 12) return 'medium';
114
+ return 'strong';
115
+ }
116
+
117
+ function median(values = []) {
118
+ const normalized = values.filter((value) => Number.isFinite(value)).sort((a, b) => a - b);
119
+ if (normalized.length === 0) return null;
120
+ const middle = Math.floor(normalized.length / 2);
121
+ if (normalized.length % 2 === 1) return normalized[middle];
122
+ return (normalized[middle - 1] + normalized[middle]) / 2;
123
+ }
124
+
125
+ function detectPlatformPhysicsProfiles({
126
+ platformProfile,
127
+ transitionDurations,
128
+ transitionTimingFunctions,
129
+ componentSemantics,
130
+ touchTargetSizes,
131
+ safeAreaTopInset,
132
+ safeAreaBottomInset,
133
+ }) {
134
+ const profiles = [];
135
+ const meaningfulDurations = transitionDurations.filter((value) => Number.isFinite(value) && value > 0);
136
+ const durationMedian = median(meaningfulDurations);
137
+ const semantics = new Set(componentSemantics);
138
+ const timings = transitionTimingFunctions.map((entry) => entry.toLowerCase());
139
+ const hasBezier = timings.some((entry) => entry.includes('cubic-bezier'));
140
+ const hasEaseFamily = timings.some((entry) => entry.includes('ease'));
141
+ const maxTouch = touchTargetSizes.length > 0 ? Math.max(...touchTargetSizes) : null;
142
+ const hasSafeArea = Number.isFinite(safeAreaTopInset) && safeAreaTopInset > 0
143
+ || Number.isFinite(safeAreaBottomInset) && safeAreaBottomInset > 0;
144
+
145
+ if (
146
+ platformProfile === 'ios_phone'
147
+ && maxTouch !== null && maxTouch >= 44
148
+ && (
149
+ (durationMedian !== null && durationMedian >= 180 && durationMedian <= 460)
150
+ || hasBezier
151
+ || hasEaseFamily
152
+ )
153
+ && (semantics.has('bottom_sheet') || semantics.has('tab_bar') || semantics.has('segmented_control') || hasSafeArea)
154
+ ) {
155
+ profiles.push('ios_spring');
156
+ }
157
+
158
+ if (
159
+ platformProfile === 'android_phone'
160
+ && maxTouch !== null && maxTouch >= 40
161
+ && (
162
+ (durationMedian !== null && durationMedian >= 90 && durationMedian <= 280)
163
+ || hasBezier
164
+ || hasEaseFamily
165
+ )
166
+ && (semantics.has('floating_primary_action') || semantics.has('tab_bar') || semantics.has('pull_to_refresh_indicator'))
167
+ ) {
168
+ profiles.push('android_ripple');
169
+ }
170
+
171
+ if (
172
+ platformProfile === 'mobile_h5'
173
+ && maxTouch !== null && maxTouch >= 40
174
+ && (
175
+ (durationMedian !== null && durationMedian >= 100 && durationMedian <= 320)
176
+ || timings.some((entry) => entry !== 'linear')
177
+ )
178
+ ) {
179
+ profiles.push('mobile_h5_smooth');
180
+ }
181
+
182
+ return profiles.sort();
183
+ }
184
+
185
+ export function exportVisualTokensFromSnapshot(snapshot) {
186
+ const nodes = pickNodes(snapshot);
187
+ const viewportMetrics = snapshot?.viewport_metrics && typeof snapshot.viewport_metrics === 'object'
188
+ ? snapshot.viewport_metrics
189
+ : {};
190
+ const fontFamilies = [];
191
+ const fontWeights = [];
192
+ const textSizes = [];
193
+ const lineHeights = [];
194
+ const letterSpacings = [];
195
+ const radiusValues = [];
196
+ const spacingValues = [];
197
+ const borderWidths = [];
198
+ const borderStyles = [];
199
+ const shadowSignatures = [];
200
+ const borderWeightTiers = [];
201
+ const shadowStrengthTiers = [];
202
+ const iconDensityValues = [];
203
+ const iconSizeValues = [];
204
+ const touchTargetSizes = [];
205
+ let bottomSheetHandlePresent = false;
206
+ let tabbarActiveStatePresent = false;
207
+ let floatingPrimaryActionPresent = false;
208
+ let segmentedControlActivePresent = false;
209
+ let loadingStatePresent = false;
210
+ let skeletonStatePresent = false;
211
+ let errorStatePresent = false;
212
+ let searchActivePresent = false;
213
+ const transitionDurations = [];
214
+ const transitionTimingFunctions = [];
215
+ const animationDurations = [];
216
+ const listRowHeights = [];
217
+ const toolbarControlDensityValues = [];
218
+ const detailBlockSpacingValues = [];
219
+ const toolbarControlStyleEntries = [];
220
+ const listRowStyleEntries = [];
221
+ const detailBlockStyleEntries = [];
222
+ const colorRoles = {};
223
+ const componentSemantics = new Set();
224
+
225
+ for (const node of nodes) {
226
+ const styles = node?.computedStyle;
227
+ if (!styles || typeof styles !== 'object') continue;
228
+
229
+ const fontFamily = typeof styles.fontFamily === 'string' ? styles.fontFamily.trim() : null;
230
+ if (fontFamily) {
231
+ fontFamily.split(',')
232
+ .map((entry) => entry.trim().replace(/^["']|["']$/g, ''))
233
+ .filter(Boolean)
234
+ .forEach((entry) => pushUnique(fontFamilies, entry));
235
+ }
236
+
237
+ pushUnique(fontWeights, toNumber(styles.fontWeight));
238
+ pushUnique(textSizes, toNumber(styles.fontSize));
239
+ pushUnique(lineHeights, toNumber(styles.lineHeight));
240
+ pushUnique(letterSpacings, toNumber(styles.letterSpacing));
241
+
242
+ for (const key of ['borderRadius', 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomLeftRadius', 'borderBottomRightRadius']) {
243
+ pushUnique(radiusValues, toNumber(styles[key]));
244
+ }
245
+
246
+ pushUnique(borderWidths, toNumber(styles.borderWidth));
247
+ pushUnique(borderWeightTiers, classifyBorderWeight(toNumber(styles.borderWidth)));
248
+ if (typeof styles.borderStyle === 'string') {
249
+ const normalizedBorderStyle = styles.borderStyle.trim().toLowerCase();
250
+ if (normalizedBorderStyle && normalizedBorderStyle !== 'none') pushUnique(borderStyles, normalizedBorderStyle);
251
+ }
252
+ if (typeof styles.boxShadow === 'string') {
253
+ const normalizedShadow = styles.boxShadow.trim().toLowerCase();
254
+ if (normalizedShadow && normalizedShadow !== 'none') {
255
+ pushUnique(shadowSignatures, normalizedShadow);
256
+ pushUnique(shadowStrengthTiers, classifyShadowStrength(normalizedShadow));
257
+ }
258
+ }
259
+ for (const duration of toDurationMs(styles.transitionDuration)) pushUnique(transitionDurations, duration);
260
+ for (const timingFn of toStringList(styles.transitionTimingFunction)) pushUnique(transitionTimingFunctions, timingFn);
261
+ for (const duration of toDurationMs(styles.animationDuration)) pushUnique(animationDurations, duration);
262
+
263
+ for (const key of ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft', 'rowGap', 'columnGap', 'gap']) {
264
+ pushUnique(spacingValues, toNumber(styles[key]));
265
+ }
266
+
267
+ const panelId = typeof node.panel_id === 'string' ? node.panel_id : null;
268
+ const backgroundColor = normalizeColor(styles.backgroundColor);
269
+ const textColor = normalizeColor(styles.color);
270
+ const borderColor = normalizeColor(styles.borderColor);
271
+
272
+ if (!colorRoles.canvas && panelId === 'main' && backgroundColor) colorRoles.canvas = backgroundColor;
273
+ if (!colorRoles.surface && panelId && ['sidebar', 'detail', 'list', 'filters', 'toolbar'].includes(panelId) && backgroundColor) {
274
+ colorRoles.surface = backgroundColor;
275
+ }
276
+ if (!colorRoles.text_primary && textColor) colorRoles.text_primary = textColor;
277
+ if (!colorRoles.border_subtle && borderColor && borderColor !== 'rgba(0, 0, 0, 0)' && borderColor !== 'transparent') {
278
+ colorRoles.border_subtle = borderColor;
279
+ }
280
+ if (!colorRoles.accent && backgroundColor && panelId && ['accent', 'cta', 'primary-action'].includes(panelId)) {
281
+ colorRoles.accent = backgroundColor;
282
+ }
283
+ if (panelId && ['sheet', 'bottom_sheet'].includes(panelId)) componentSemantics.add('bottom_sheet');
284
+ if (panelId && ['bottom_bar', 'tabbar', 'tab_bar'].includes(panelId)) componentSemantics.add('tab_bar');
285
+ if (panelId && ['primary_action', 'fab'].includes(panelId)) componentSemantics.add('floating_primary_action');
286
+ if (panelId && ['filters', 'segments', 'segmented'].includes(panelId)) componentSemantics.add('segmented_control');
287
+ if (panelId && ['search', 'search_bar'].includes(panelId)) componentSemantics.add('search_bar');
288
+ if (panelId && ['snackbar', 'toast'].includes(panelId)) componentSemantics.add('snackbar');
289
+ if (panelId && ['refresh', 'refresh_indicator', 'pull_to_refresh'].includes(panelId)) componentSemantics.add('pull_to_refresh_indicator');
290
+ if (panelId && ['loading', 'loading_state', 'progress'].includes(panelId)) componentSemantics.add('loading_state');
291
+ if (panelId && ['skeleton', 'skeleton_state'].includes(panelId)) componentSemantics.add('skeleton_state');
292
+ if (panelId && ['error', 'error_state'].includes(panelId)) componentSemantics.add('error_state');
293
+ if (panelId && ['search_active', 'search_results'].includes(panelId)) componentSemantics.add('search_active');
294
+
295
+ const iconDensity = toNumber(node?.descendantMetrics?.icon_density);
296
+ pushUnique(iconDensityValues, iconDensity);
297
+ if (Array.isArray(node?.descendantMetrics?.icon_rects)) {
298
+ for (const rect of node.descendantMetrics.icon_rects) {
299
+ const width = toNumber(rect?.width);
300
+ const height = toNumber(rect?.height);
301
+ pushUnique(iconSizeValues, width);
302
+ pushUnique(iconSizeValues, height);
303
+ }
304
+ }
305
+ if (Array.isArray(node?.descendantMetrics?.row_rects)) {
306
+ for (const rect of node.descendantMetrics.row_rects) {
307
+ pushUnique(listRowHeights, toNumber(rect?.height));
308
+ }
309
+ }
310
+ if (Array.isArray(node?.descendantMetrics?.toolbar_control_styles)) {
311
+ toolbarControlStyleEntries.push(...node.descendantMetrics.toolbar_control_styles);
312
+ }
313
+ if (Array.isArray(node?.descendantMetrics?.list_row_styles)) {
314
+ listRowStyleEntries.push(...node.descendantMetrics.list_row_styles);
315
+ }
316
+ if (Array.isArray(node?.descendantMetrics?.detail_block_styles)) {
317
+ detailBlockStyleEntries.push(...node.descendantMetrics.detail_block_styles);
318
+ }
319
+ const controlDensity = toNumber(node?.descendantMetrics?.control_density);
320
+ if (node?.panel_id === 'toolbar') pushUnique(toolbarControlDensityValues, controlDensity);
321
+ if (Array.isArray(node?.descendantMetrics?.control_rects)) {
322
+ for (const rect of node.descendantMetrics.control_rects) {
323
+ const width = toNumber(rect?.width);
324
+ const height = toNumber(rect?.height);
325
+ if (Number.isFinite(width) && Number.isFinite(height)) pushUnique(touchTargetSizes, Math.min(width, height));
326
+ }
327
+ }
328
+ if (node?.descendantMetrics?.bottom_sheet_handle_present === true) bottomSheetHandlePresent = true;
329
+ if (node?.descendantMetrics?.active_tab_present === true) tabbarActiveStatePresent = true;
330
+ if (node?.descendantMetrics?.floating_primary_action_present === true) floatingPrimaryActionPresent = true;
331
+ if (node?.descendantMetrics?.segmented_control_active_present === true) segmentedControlActivePresent = true;
332
+ if (node?.descendantMetrics?.loading_state_present === true) loadingStatePresent = true;
333
+ if (node?.descendantMetrics?.skeleton_state_present === true) skeletonStatePresent = true;
334
+ if (node?.descendantMetrics?.error_state_present === true) errorStatePresent = true;
335
+ if (node?.descendantMetrics?.search_active_present === true) searchActivePresent = true;
336
+ if (node?.descendantMetrics?.bottom_sheet_handle_present === true) componentSemantics.add('bottom_sheet');
337
+ if (node?.descendantMetrics?.active_tab_present === true) componentSemantics.add('tab_bar');
338
+ if (node?.descendantMetrics?.floating_primary_action_present === true) componentSemantics.add('floating_primary_action');
339
+ if (node?.descendantMetrics?.segmented_control_active_present === true) componentSemantics.add('segmented_control');
340
+ if (node?.descendantMetrics?.search_bar_present === true) componentSemantics.add('search_bar');
341
+ if (node?.descendantMetrics?.loading_state_present === true) componentSemantics.add('loading_state');
342
+ if (node?.descendantMetrics?.skeleton_state_present === true) componentSemantics.add('skeleton_state');
343
+ if (node?.descendantMetrics?.error_state_present === true) componentSemantics.add('error_state');
344
+ if (node?.descendantMetrics?.search_active_present === true) componentSemantics.add('search_active');
345
+ if (Array.isArray(node?.descendantMetrics?.detail_block_gaps)) {
346
+ for (const gap of node.descendantMetrics.detail_block_gaps) {
347
+ if (node?.panel_id === 'detail') pushUnique(detailBlockSpacingValues, toNumber(gap));
348
+ }
349
+ }
350
+ }
351
+
352
+ const safeAreaTopInset = toNumber(viewportMetrics.safe_area_top);
353
+ const safeAreaBottomInset = toNumber(viewportMetrics.safe_area_bottom);
354
+ const platformPhysicsProfiles = detectPlatformPhysicsProfiles({
355
+ platformProfile: typeof snapshot?.platform_profile === 'string' ? snapshot.platform_profile : null,
356
+ transitionDurations,
357
+ transitionTimingFunctions,
358
+ componentSemantics: Array.from(componentSemantics),
359
+ touchTargetSizes,
360
+ safeAreaTopInset,
361
+ safeAreaBottomInset,
362
+ });
363
+
364
+ return {
365
+ observed_visual_tokens: {
366
+ font_families: fontFamilies,
367
+ font_weights: fontWeights.sort((a, b) => a - b),
368
+ text_sizes: textSizes.sort((a, b) => a - b),
369
+ line_heights: lineHeights.sort((a, b) => a - b),
370
+ letter_spacings: letterSpacings.sort((a, b) => a - b),
371
+ radius_values: radiusValues.sort((a, b) => a - b),
372
+ border_widths: borderWidths.sort((a, b) => a - b),
373
+ border_styles: borderStyles.sort(),
374
+ border_weight_tiers: borderWeightTiers.sort(),
375
+ shadow_signatures: shadowSignatures.sort(),
376
+ shadow_strength_tiers: shadowStrengthTiers.sort(),
377
+ touch_target_sizes: touchTargetSizes.sort((a, b) => a - b),
378
+ safe_area_top_inset: toNumber(viewportMetrics.safe_area_top),
379
+ safe_area_bottom_inset: toNumber(viewportMetrics.safe_area_bottom),
380
+ keyboard_inset_bottom: toNumber(viewportMetrics.keyboard_inset_bottom),
381
+ bottom_sheet_handle_present: bottomSheetHandlePresent,
382
+ tabbar_active_state_present: tabbarActiveStatePresent,
383
+ floating_primary_action_present: floatingPrimaryActionPresent,
384
+ segmented_control_active_state_present: segmentedControlActivePresent,
385
+ loading_state_present: loadingStatePresent,
386
+ skeleton_state_present: skeletonStatePresent,
387
+ error_state_present: errorStatePresent,
388
+ search_active_present: searchActivePresent,
389
+ component_semantics: Array.from(componentSemantics).sort(),
390
+ platform_physics_profiles: platformPhysicsProfiles,
391
+ transition_durations: transitionDurations.sort((a, b) => a - b),
392
+ transition_timing_functions: transitionTimingFunctions.sort(),
393
+ animation_durations: animationDurations.sort((a, b) => a - b),
394
+ icon_density_values: iconDensityValues.sort((a, b) => a - b),
395
+ icon_size_values: iconSizeValues.sort((a, b) => a - b),
396
+ list_row_heights: listRowHeights.sort((a, b) => a - b),
397
+ toolbar_control_density_values: toolbarControlDensityValues.sort((a, b) => a - b),
398
+ detail_block_spacing_values: detailBlockSpacingValues.sort((a, b) => a - b),
399
+ component_family_consistency: {
400
+ toolbar_controls: summarizeFamilyStyles(toolbarControlStyleEntries),
401
+ list_rows: summarizeFamilyStyles(listRowStyleEntries),
402
+ detail_blocks: summarizeFamilyStyles(detailBlockStyleEntries),
403
+ },
404
+ color_roles: colorRoles,
405
+ spacing_values: spacingValues.sort((a, b) => a - b),
406
+ },
407
+ input_nodes: nodes.length,
408
+ };
409
+ }
410
+
411
+ export function exportVisualTokensToFile({ inputPath, outputPath }) {
412
+ const snapshot = JSON.parse(readFileSync(inputPath, 'utf8'));
413
+ const payload = exportVisualTokensFromSnapshot(snapshot);
414
+ mkdirSync(dirname(outputPath), { recursive: true });
415
+ writeFileSync(outputPath, JSON.stringify(payload, null, 2));
416
+ return payload;
417
+ }