@holoscript/std 6.0.3 → 7.0.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 (128) hide show
  1. package/README.md +8 -0
  2. package/dist/chunk-DGUM43GV.js +11 -0
  3. package/dist/chunk-DGUM43GV.js.map +1 -0
  4. package/dist/{chunk-PFX2JM4X.js → chunk-FAMOLKH3.js} +2 -1
  5. package/dist/{chunk-PFX2JM4X.js.map → chunk-FAMOLKH3.js.map} +1 -1
  6. package/dist/{chunk-7HVUYGPS.js → chunk-SOKH6FJ7.js} +37 -5
  7. package/dist/chunk-SOKH6FJ7.js.map +1 -0
  8. package/dist/{chunk-W2Q3LUCM.js → chunk-XJIFG7G3.js} +3 -3
  9. package/dist/chunk-XJIFG7G3.js.map +1 -0
  10. package/dist/collections.cjs +1 -0
  11. package/dist/collections.cjs.map +1 -1
  12. package/dist/collections.js +2 -1
  13. package/dist/fs/fs.d.ts +288 -0
  14. package/dist/fs/fs.d.ts.map +1 -0
  15. package/dist/fs/index.cjs +1049 -0
  16. package/dist/fs/index.cjs.map +1 -0
  17. package/dist/fs/index.d.ts +51 -0
  18. package/dist/fs/index.d.ts.map +1 -0
  19. package/dist/fs/index.js +918 -0
  20. package/dist/fs/index.js.map +1 -0
  21. package/dist/fs/path.d.ts +135 -0
  22. package/dist/fs/path.d.ts.map +1 -0
  23. package/dist/fs/sandbox.d.ts +49 -0
  24. package/dist/fs/sandbox.d.ts.map +1 -0
  25. package/dist/fs/watch.d.ts +133 -0
  26. package/dist/fs/watch.d.ts.map +1 -0
  27. package/dist/index.cjs +54 -7
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.js +19 -4
  30. package/dist/index.js.map +1 -1
  31. package/dist/math.cjs +21 -2
  32. package/dist/math.cjs.map +1 -1
  33. package/dist/math.js +2 -1
  34. package/dist/string.cjs +2 -2
  35. package/dist/string.cjs.map +1 -1
  36. package/dist/string.js +2 -1
  37. package/dist/time.js +1 -0
  38. package/dist/traits/EconomicPrimitives.js +1 -0
  39. package/dist/traits/EconomicTraits.js +1 -0
  40. package/dist/traits/VRRTraits.d.ts +2 -2
  41. package/dist/types.d.ts.map +1 -1
  42. package/package.json +24 -9
  43. package/CHANGELOG.md +0 -7
  44. package/dist/__tests__/EconomicPrimitives.test.d.ts +0 -2
  45. package/dist/__tests__/EconomicPrimitives.test.d.ts.map +0 -1
  46. package/dist/__tests__/EconomicTraits.test.d.ts +0 -2
  47. package/dist/__tests__/EconomicTraits.test.d.ts.map +0 -1
  48. package/dist/__tests__/SimulationLabPrimitives.test.d.ts +0 -2
  49. package/dist/__tests__/SimulationLabPrimitives.test.d.ts.map +0 -1
  50. package/dist/__tests__/collections.test.d.ts +0 -2
  51. package/dist/__tests__/collections.test.d.ts.map +0 -1
  52. package/dist/__tests__/events.test.d.ts +0 -2
  53. package/dist/__tests__/events.test.d.ts.map +0 -1
  54. package/dist/__tests__/index.test.d.ts +0 -2
  55. package/dist/__tests__/index.test.d.ts.map +0 -1
  56. package/dist/__tests__/materials.test.d.ts +0 -2
  57. package/dist/__tests__/materials.test.d.ts.map +0 -1
  58. package/dist/__tests__/math.test.d.ts +0 -2
  59. package/dist/__tests__/math.test.d.ts.map +0 -1
  60. package/dist/__tests__/physics.test.d.ts +0 -2
  61. package/dist/__tests__/physics.test.d.ts.map +0 -1
  62. package/dist/__tests__/spatial.test.d.ts +0 -2
  63. package/dist/__tests__/spatial.test.d.ts.map +0 -1
  64. package/dist/__tests__/standard-library-fundamentals.test.d.ts +0 -2
  65. package/dist/__tests__/standard-library-fundamentals.test.d.ts.map +0 -1
  66. package/dist/__tests__/string-and-time-utilities.test.d.ts +0 -2
  67. package/dist/__tests__/string-and-time-utilities.test.d.ts.map +0 -1
  68. package/dist/__tests__/string-decoupled.test.d.ts +0 -2
  69. package/dist/__tests__/string-decoupled.test.d.ts.map +0 -1
  70. package/dist/__tests__/string.test.d.ts +0 -2
  71. package/dist/__tests__/string.test.d.ts.map +0 -1
  72. package/dist/__tests__/time.test.d.ts +0 -2
  73. package/dist/__tests__/time.test.d.ts.map +0 -1
  74. package/dist/chunk-7HVUYGPS.js.map +0 -1
  75. package/dist/chunk-W2Q3LUCM.js.map +0 -1
  76. package/dist/string.basic.test.d.ts +0 -5
  77. package/dist/string.basic.test.d.ts.map +0 -1
  78. package/dist/string.test.d.ts +0 -5
  79. package/dist/string.test.d.ts.map +0 -1
  80. package/dist/traits/__tests__/ARTraits.test.d.ts +0 -2
  81. package/dist/traits/__tests__/ARTraits.test.d.ts.map +0 -1
  82. package/dist/traits/__tests__/EconomicPrimitives.test.d.ts +0 -2
  83. package/dist/traits/__tests__/EconomicPrimitives.test.d.ts.map +0 -1
  84. package/dist/traits/__tests__/EconomicTraits.test.d.ts +0 -2
  85. package/dist/traits/__tests__/EconomicTraits.test.d.ts.map +0 -1
  86. package/error-utf8.log +0 -6
  87. package/error.log +0 -0
  88. package/src/__tests__/EconomicPrimitives.test.ts +0 -690
  89. package/src/__tests__/EconomicTraits.test.ts +0 -425
  90. package/src/__tests__/SimulationLabPrimitives.test.ts +0 -415
  91. package/src/__tests__/collections.test.ts +0 -182
  92. package/src/__tests__/events.test.ts +0 -135
  93. package/src/__tests__/index.test.ts +0 -14
  94. package/src/__tests__/materials.test.ts +0 -84
  95. package/src/__tests__/math.test.ts +0 -246
  96. package/src/__tests__/physics.test.ts +0 -83
  97. package/src/__tests__/spatial.test.ts +0 -333
  98. package/src/__tests__/standard-library-fundamentals.test.ts +0 -1301
  99. package/src/__tests__/string-and-time-utilities.test.ts +0 -1070
  100. package/src/__tests__/string-decoupled.test.ts +0 -14
  101. package/src/__tests__/string.test.ts +0 -164
  102. package/src/__tests__/time.test.ts +0 -110
  103. package/src/collections.ts +0 -850
  104. package/src/events.ts +0 -88
  105. package/src/index.ts +0 -474
  106. package/src/materials.ts +0 -109
  107. package/src/math.ts +0 -674
  108. package/src/physics.ts +0 -141
  109. package/src/spatial.ts +0 -320
  110. package/src/string.basic.test.ts +0 -14
  111. package/src/string.test.ts +0 -335
  112. package/src/string.ts +0 -531
  113. package/src/time.ts +0 -621
  114. package/src/traits/ARTraits.ts +0 -103
  115. package/src/traits/EconomicPrimitives.ts +0 -755
  116. package/src/traits/EconomicTraits.ts +0 -552
  117. package/src/traits/IoTTraits.ts +0 -102
  118. package/src/traits/SimulationLabPrimitives.ts +0 -650
  119. package/src/traits/SimulationLabTraits.ts +0 -191
  120. package/src/traits/VRRTraits.ts +0 -326
  121. package/src/traits/__tests__/ARTraits.test.ts +0 -14
  122. package/src/traits/__tests__/EconomicPrimitives.test.ts +0 -14
  123. package/src/traits/__tests__/EconomicTraits.test.ts +0 -14
  124. package/src/types.ts +0 -411
  125. package/std_error.log +0 -5
  126. package/tsconfig.json +0 -18
  127. package/tsup.config.ts +0 -17
  128. package/vitest.config.ts +0 -10
package/src/string.ts DELETED
@@ -1,531 +0,0 @@
1
- /**
2
- * @holoscript/std - String Module
3
- *
4
- * String manipulation utilities for HoloScript Plus programs.
5
- */
6
-
7
- /**
8
- * Check if a string is empty or only whitespace
9
- */
10
- export function isBlank(s: string): boolean {
11
- return s.trim().length === 0;
12
- }
13
-
14
- /**
15
- * Check if a string is not empty and not only whitespace
16
- */
17
- export function isNotBlank(s: string): boolean {
18
- return s.trim().length > 0;
19
- }
20
-
21
- /**
22
- * Capitalize the first letter of a string
23
- */
24
- export function capitalize(s: string): string {
25
- if (s.length === 0) return s;
26
- return s[0].toUpperCase() + s.slice(1);
27
- }
28
-
29
- /**
30
- * Capitalize the first letter of each word
31
- */
32
- export function titleCase(s: string): string {
33
- return s.split(/\s+/).map(capitalize).join(' ');
34
- }
35
-
36
- /**
37
- * Convert to camelCase
38
- */
39
- export function camelCase(s: string): string {
40
- return s
41
- .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
42
- .replace(/^[A-Z]/, (c) => c.toLowerCase());
43
- }
44
-
45
- /**
46
- * Convert to PascalCase
47
- */
48
- export function pascalCase(s: string): string {
49
- const camel = camelCase(s);
50
- return camel.length > 0 ? camel[0].toUpperCase() + camel.slice(1) : camel;
51
- }
52
-
53
- /**
54
- * Convert to snake_case
55
- */
56
- export function snakeCase(s: string): string {
57
- return s
58
- .replace(/([a-z])([A-Z])/g, '$1_$2')
59
- .replace(/[-\s]+/g, '_')
60
- .toLowerCase();
61
- }
62
-
63
- /**
64
- * Convert to kebab-case
65
- */
66
- export function kebabCase(s: string): string {
67
- return s
68
- .replace(/([a-z])([A-Z])/g, '$1-$2')
69
- .replace(/[_\s]+/g, '-')
70
- .toLowerCase();
71
- }
72
-
73
- /**
74
- * Convert to SCREAMING_SNAKE_CASE
75
- */
76
- export function constantCase(s: string): string {
77
- return snakeCase(s).toUpperCase();
78
- }
79
-
80
- /**
81
- * Pad a string on the left to reach target length
82
- */
83
- export function padLeft(s: string, length: number, char = ' '): string {
84
- return s.padStart(length, char);
85
- }
86
-
87
- /**
88
- * Pad a string on the right to reach target length
89
- */
90
- export function padRight(s: string, length: number, char = ' '): string {
91
- return s.padEnd(length, char);
92
- }
93
-
94
- /**
95
- * Pad a string on both sides to center it
96
- */
97
- export function center(s: string, length: number, char = ' '): string {
98
- if (s.length >= length) return s;
99
- const totalPadding = length - s.length;
100
- const leftPadding = Math.floor(totalPadding / 2);
101
- const rightPadding = totalPadding - leftPadding;
102
- return char.repeat(leftPadding) + s + char.repeat(rightPadding);
103
- }
104
-
105
- /**
106
- * Truncate a string to max length, adding ellipsis if needed
107
- */
108
- export function truncate(s: string, maxLength: number, ellipsis = '...'): string {
109
- if (s.length <= maxLength) return s;
110
- return s.slice(0, maxLength - ellipsis.length) + ellipsis;
111
- }
112
-
113
- /**
114
- * Truncate a string in the middle
115
- */
116
- export function truncateMiddle(s: string, maxLength: number, ellipsis = '...'): string {
117
- if (s.length <= maxLength) return s;
118
- const availableLength = maxLength - ellipsis.length;
119
- const leftLength = Math.floor(availableLength / 2);
120
- const rightLength = Math.ceil(availableLength / 2);
121
- return s.slice(0, leftLength) + ellipsis + s.slice(-rightLength);
122
- }
123
-
124
- /**
125
- * Repeat a string n times
126
- */
127
- export function repeat(s: string, count: number): string {
128
- return s.repeat(count);
129
- }
130
-
131
- /**
132
- * Reverse a string
133
- */
134
- export function reverse(s: string): string {
135
- return [...s].reverse().join('');
136
- }
137
-
138
- /**
139
- * Count occurrences of a substring
140
- */
141
- export function count(s: string, substring: string): number {
142
- if (substring.length === 0) return 0;
143
- let count = 0;
144
- let pos = 0;
145
- while ((pos = s.indexOf(substring, pos)) !== -1) {
146
- count++;
147
- pos += 1;
148
- }
149
- return count;
150
- }
151
-
152
- /**
153
- * Check if string contains a substring (case-insensitive)
154
- */
155
- export function containsIgnoreCase(s: string, substring: string): boolean {
156
- return s.toLowerCase().includes(substring.toLowerCase());
157
- }
158
-
159
- /**
160
- * Check if string starts with prefix (case-insensitive)
161
- */
162
- export function startsWithIgnoreCase(s: string, prefix: string): boolean {
163
- return s.toLowerCase().startsWith(prefix.toLowerCase());
164
- }
165
-
166
- /**
167
- * Check if string ends with suffix (case-insensitive)
168
- */
169
- export function endsWithIgnoreCase(s: string, suffix: string): boolean {
170
- return s.toLowerCase().endsWith(suffix.toLowerCase());
171
- }
172
-
173
- /**
174
- * Remove all whitespace from a string
175
- */
176
- export function removeWhitespace(s: string): string {
177
- return s.replace(/\s+/g, '');
178
- }
179
-
180
- /**
181
- * Collapse multiple spaces into single space
182
- */
183
- export function collapseWhitespace(s: string): string {
184
- return s.replace(/\s+/g, ' ').trim();
185
- }
186
-
187
- /**
188
- * Remove a prefix if present
189
- */
190
- export function removePrefix(s: string, prefix: string): string {
191
- return s.startsWith(prefix) ? s.slice(prefix.length) : s;
192
- }
193
-
194
- /**
195
- * Remove a suffix if present
196
- */
197
- export function removeSuffix(s: string, suffix: string): string {
198
- return s.endsWith(suffix) ? s.slice(0, -suffix.length) : s;
199
- }
200
-
201
- /**
202
- * Wrap a string with prefix and suffix
203
- */
204
- export function wrap(s: string, wrapper: string): string;
205
- export function wrap(s: string, prefix: string, suffix: string): string;
206
- export function wrap(s: string, prefixOrWrapper: string, suffix?: string): string {
207
- if (suffix === undefined) {
208
- return prefixOrWrapper + s + prefixOrWrapper;
209
- }
210
- return prefixOrWrapper + s + suffix;
211
- }
212
-
213
- /**
214
- * Unwrap a string by removing matching prefix and suffix
215
- */
216
- export function unwrap(s: string, wrapper: string): string;
217
- export function unwrap(s: string, prefix: string, suffix: string): string;
218
- export function unwrap(s: string, prefixOrWrapper: string, suffix?: string): string {
219
- const prefix = prefixOrWrapper;
220
- const actualSuffix = suffix ?? prefixOrWrapper;
221
- if (s.startsWith(prefix) && s.endsWith(actualSuffix)) {
222
- return s.slice(prefix.length, -actualSuffix.length || undefined);
223
- }
224
- return s;
225
- }
226
-
227
- /**
228
- * Split a string into lines
229
- */
230
- export function lines(s: string): string[] {
231
- return s.split(/\r?\n/);
232
- }
233
-
234
- /**
235
- * Split a string into words
236
- */
237
- export function words(s: string): string[] {
238
- return s.split(/\s+/).filter((w) => w.length > 0);
239
- }
240
-
241
- /**
242
- * Split a string into characters
243
- */
244
- export function chars(s: string): string[] {
245
- return [...s];
246
- }
247
-
248
- /**
249
- * Join strings with a separator
250
- */
251
- export function join(strings: string[], separator = ''): string {
252
- return strings.join(separator);
253
- }
254
-
255
- /**
256
- * Format a template string with values
257
- */
258
- export function format(template: string, values: Record<string, unknown>): string {
259
- return template.replace(/\{(\w+)\}/g, (match, key) => {
260
- return key in values ? String(values[key]) : match;
261
- });
262
- }
263
-
264
- /**
265
- * Format a number as a string with grouping
266
- */
267
- export function formatNumber(n: number, options: Intl.NumberFormatOptions = {}): string {
268
- return n.toLocaleString('en-US', options);
269
- }
270
-
271
- /**
272
- * Format a number with commas
273
- */
274
- export function numberWithCommas(n: number): string {
275
- return n.toLocaleString('en-US');
276
- }
277
-
278
- /**
279
- * Format bytes as human-readable string
280
- */
281
- export function formatBytes(bytes: number, decimals = 2): string {
282
- if (bytes === 0) return '0 B';
283
- const k = 1024;
284
- if (bytes < 0) return `${bytes} B`;
285
- const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
286
- const i = Math.floor(Math.log(bytes) / Math.log(k));
287
- return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
288
- }
289
-
290
- /**
291
- * Format duration in milliseconds as human-readable string
292
- */
293
- export function formatDuration(ms: number): string {
294
- if (ms < 1000) return `${ms}ms`;
295
- if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
296
- if (ms < 3600000) {
297
- return `${(ms / 60000).toFixed(1)}m`;
298
- }
299
- const hours = Math.floor(ms / 3600000);
300
- const mins = Math.floor((ms % 3600000) / 60000);
301
- return `${(ms / 3600000).toFixed(1)}h`;
302
- }
303
-
304
- /**
305
- * Escape HTML special characters
306
- */
307
- export function escapeHtml(s: string): string {
308
- const htmlEntities: Record<string, string> = {
309
- '&': '&amp;',
310
- '<': '&lt;',
311
- '>': '&gt;',
312
- '"': '&quot;',
313
- "'": '&#39;',
314
- };
315
- return s.replace(/[&<>"']/g, (c) => htmlEntities[c]);
316
- }
317
-
318
- /**
319
- * Unescape HTML entities
320
- */
321
- export function unescapeHtml(s: string): string {
322
- const htmlEntities: Record<string, string> = {
323
- '&amp;': '&',
324
- '&lt;': '<',
325
- '&gt;': '>',
326
- '&quot;': '"',
327
- '&#39;': "'",
328
- '&#x27;': "'",
329
- '&#x2F;': '/',
330
- };
331
- return s.replace(
332
- /&(?:amp|lt|gt|quot|#39|#x27|#x2F);/g,
333
- (entity) => htmlEntities[entity] || entity
334
- );
335
- }
336
-
337
- /**
338
- * Escape regex special characters
339
- */
340
- export function escapeRegex(s: string): string {
341
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
342
- }
343
-
344
- /**
345
- * Generate a slug from a string
346
- */
347
- export function slugify(s: string): string {
348
- return s
349
- .toLowerCase()
350
- .normalize('NFD')
351
- .replace(/[\u0300-\u036f]/g, '') // Remove diacritics
352
- .replace(/[^a-z0-9]+/g, '-')
353
- .replace(/^-+|-+$/g, '');
354
- }
355
-
356
- /**
357
- * Check if string is a valid identifier
358
- */
359
- export function isValidIdentifier(s: string): boolean {
360
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(s);
361
- }
362
-
363
- /**
364
- * Check if string is numeric
365
- */
366
- export function isNumeric(s: string): boolean {
367
- return !isNaN(parseFloat(s)) && isFinite(Number(s));
368
- }
369
-
370
- /**
371
- * Check if string is alphanumeric
372
- */
373
- export function isAlphanumeric(s: string): boolean {
374
- return /^[a-zA-Z0-9]+$/.test(s);
375
- }
376
-
377
- /**
378
- * Check if string is alphabetic
379
- */
380
- export function isAlpha(s: string): boolean {
381
- return /^[a-zA-Z]+$/.test(s);
382
- }
383
-
384
- /**
385
- * Generate a random string
386
- */
387
- export function randomString(
388
- length: number,
389
- charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
390
- ): string {
391
- let result = '';
392
- for (let i = 0; i < length; i++) {
393
- result += charset[Math.floor(Math.random() * charset.length)];
394
- }
395
- return result;
396
- }
397
-
398
- /**
399
- * Generate a UUID v4
400
- */
401
- export function uuid(): string {
402
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
403
- const r = (Math.random() * 16) | 0;
404
- const v = c === 'x' ? r : (r & 0x3) | 0x8;
405
- return v.toString(16);
406
- });
407
- }
408
-
409
- /**
410
- * Indent each line of a string
411
- */
412
- export function indent(s: string, spaces: number, char = ' '): string {
413
- const prefix = char.repeat(spaces);
414
- return s
415
- .split('\n')
416
- .map((line) => prefix + line)
417
- .join('\n');
418
- }
419
-
420
- /**
421
- * Dedent a string (remove common leading whitespace)
422
- */
423
- export function dedent(s: string): string {
424
- const linesList = lines(s);
425
- const nonEmptyLines = linesList.filter((l) => l.trim().length > 0);
426
- if (nonEmptyLines.length === 0) return s;
427
-
428
- const minIndent = Math.min(
429
- ...nonEmptyLines.map((line) => {
430
- const match = line.match(/^(\s*)/);
431
- return match ? match[1].length : 0;
432
- })
433
- );
434
-
435
- return linesList.map((line) => line.slice(minIndent)).join('\n');
436
- }
437
-
438
- /**
439
- * Word wrap text to a maximum width
440
- */
441
- export function wordWrap(s: string, maxWidth: number): string {
442
- const wordsList = words(s);
443
- const linesList: string[] = [];
444
- let currentLine = '';
445
-
446
- for (const word of wordsList) {
447
- if (currentLine.length === 0) {
448
- currentLine = word;
449
- } else if (currentLine.length + 1 + word.length <= maxWidth) {
450
- currentLine += ' ' + word;
451
- } else {
452
- linesList.push(currentLine);
453
- currentLine = word;
454
- }
455
- }
456
-
457
- if (currentLine.length > 0) {
458
- linesList.push(currentLine);
459
- }
460
-
461
- return linesList.join('\n');
462
- }
463
-
464
- /**
465
- * Levenshtein distance between two strings
466
- */
467
- export function levenshtein(a: string, b: string): number {
468
- if (a.length === 0) return b.length;
469
- if (b.length === 0) return a.length;
470
-
471
- const matrix: number[][] = [];
472
-
473
- for (let i = 0; i <= b.length; i++) {
474
- matrix[i] = [i];
475
- }
476
- for (let j = 0; j <= a.length; j++) {
477
- matrix[0][j] = j;
478
- }
479
-
480
- for (let i = 1; i <= b.length; i++) {
481
- for (let j = 1; j <= a.length; j++) {
482
- if (b[i - 1] === a[j - 1]) {
483
- matrix[i][j] = matrix[i - 1][j - 1];
484
- } else {
485
- matrix[i][j] = Math.min(
486
- matrix[i - 1][j - 1] + 1, // substitution
487
- matrix[i][j - 1] + 1, // insertion
488
- matrix[i - 1][j] + 1 // deletion
489
- );
490
- }
491
- }
492
- }
493
-
494
- return matrix[b.length][a.length];
495
- }
496
-
497
- function lcsLength(a: string, b: string): number {
498
- const n = b.length;
499
- const dp: number[] = new Array(n + 1).fill(0);
500
- for (let i = 0; i < a.length; i++) {
501
- let prev = 0;
502
- for (let j = 0; j < n; j++) {
503
- const tmp = dp[j + 1];
504
- dp[j + 1] = a[i] === b[j] ? prev + 1 : Math.max(dp[j + 1], dp[j]);
505
- prev = tmp;
506
- }
507
- }
508
- return dp[n];
509
- }
510
-
511
- /**
512
- * Check similarity between two strings (0-1) using Sorensen-Dice over LCS
513
- */
514
- export function similarity(a: string, b: string): number {
515
- const total = a.length + b.length;
516
- if (total === 0) return 1;
517
- return (2 * lcsLength(a, b)) / total;
518
- }
519
-
520
- /**
521
- * Extract HoloScript trait annotations (@name) and JSX component names from code
522
- */
523
- export function extractTraits(code: string): string[] {
524
- const traits = new Set<string>();
525
- let m: RegExpExecArray | null;
526
- const atRe = /@([a-zA-Z_]\w*)/g;
527
- while ((m = atRe.exec(code)) !== null) traits.add(`@${m[1]}`);
528
- const tagRe = /<([A-Z][a-zA-Z0-9]*)\b/g;
529
- while ((m = tagRe.exec(code)) !== null) traits.add(m[1]);
530
- return [...traits];
531
- }