enigma-cli 1.35.10 → 1.35.13

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.
@@ -7,28 +7,73 @@
7
7
  "name": "marquee",
8
8
  "title": "Draggable infinite marquee",
9
9
  "description": "Looping row that takes a speed in px/s, measures its own lap, and can be grabbed and thrown without swallowing the clicks on its items.",
10
- "targets": ["vanilla", "astro", "react"],
10
+ "targets": [
11
+ "vanilla",
12
+ "astro",
13
+ "react"
14
+ ],
11
15
  "entry": {
12
16
  "vanilla": "@enigmax/primitives",
13
17
  "astro": "@enigmax/primitives",
14
18
  "react": "@enigmax/primitives/react"
15
19
  },
16
20
  "exports": {
17
- "vanilla": ["createMarquee"],
18
- "astro": ["createMarquee"],
19
- "react": ["useMarquee"]
21
+ "vanilla": [
22
+ "createMarquee"
23
+ ],
24
+ "astro": [
25
+ "createMarquee"
26
+ ],
27
+ "react": [
28
+ "useMarquee"
29
+ ]
20
30
  },
21
31
  "files": [
22
32
  {
23
33
  "path": "src/core/marquee.ts",
24
- "dest": "marquee.ts",
25
- "targets": ["vanilla", "astro", "react"]
34
+ "dest": "marquee/marquee.ts",
35
+ "targets": [
36
+ "vanilla",
37
+ "astro",
38
+ "react"
39
+ ]
26
40
  },
27
41
  {
28
42
  "path": "src/react/use-marquee.ts",
29
- "dest": "use-marquee.ts",
30
- "targets": ["react"],
31
- "rewrite": { "@/core/marquee": "./marquee" }
43
+ "dest": "marquee/use-marquee.ts",
44
+ "targets": [
45
+ "react"
46
+ ],
47
+ "rewrite": {
48
+ "@/core/marquee": "./marquee"
49
+ },
50
+ "main": true
51
+ },
52
+ {
53
+ "path": "recipes/marquee/tailwind.tsx",
54
+ "dest": "marquee/Marquee.tsx",
55
+ "targets": [
56
+ "react"
57
+ ],
58
+ "style": "tailwind"
59
+ },
60
+ {
61
+ "path": "recipes/marquee/css.tsx",
62
+ "dest": "marquee/Marquee.tsx",
63
+ "targets": [
64
+ "react"
65
+ ],
66
+ "style": "css"
67
+ },
68
+ {
69
+ "path": "recipes/marquee/styles.css",
70
+ "dest": "marquee/styles.css",
71
+ "targets": [
72
+ "react",
73
+ "vanilla",
74
+ "astro"
75
+ ],
76
+ "style": "css"
32
77
  }
33
78
  ],
34
79
  "styles": false,
@@ -40,7 +85,604 @@
40
85
  "[data-enigma-marquee-track]",
41
86
  "[data-enigma-marquee-copy]"
42
87
  ],
43
- "docs": "docs/notes/primitives.md#marquee"
88
+ "docs": "docs/notes/primitives.md#marquee",
89
+ "recipes": [
90
+ "tailwind",
91
+ "css"
92
+ ]
93
+ },
94
+ {
95
+ "name": "logo-marquee",
96
+ "title": "Logo wall",
97
+ "description": "The \"trusted by\" row: a marquee that also does the job a logo wall needs doing - one optical height for logos that arrive at a dozen different sizes, colour drained so twelve brand palettes stop fighting, and text logos like NASA or Bloomberg alongside the images.",
98
+ "targets": [
99
+ "react"
100
+ ],
101
+ "entry": {
102
+ "react": "@enigmax/primitives/react"
103
+ },
104
+ "exports": {
105
+ "react": [
106
+ "LogoMarquee"
107
+ ]
108
+ },
109
+ "files": [
110
+ {
111
+ "path": "src/core/marquee.ts",
112
+ "dest": "logo-marquee/marquee.ts",
113
+ "targets": [
114
+ "react"
115
+ ]
116
+ },
117
+ {
118
+ "path": "src/react/use-marquee.ts",
119
+ "dest": "logo-marquee/use-marquee.ts",
120
+ "targets": [
121
+ "react"
122
+ ],
123
+ "rewrite": {
124
+ "@/core/marquee": "./marquee"
125
+ }
126
+ },
127
+ {
128
+ "path": "recipes/logo-marquee/styles.css",
129
+ "dest": "logo-marquee/styles.css",
130
+ "targets": [
131
+ "react"
132
+ ]
133
+ },
134
+ {
135
+ "path": "recipes/logo-marquee/index.tsx",
136
+ "dest": "logo-marquee/index.tsx",
137
+ "main": true,
138
+ "targets": [
139
+ "react"
140
+ ],
141
+ "rewrite": {
142
+ "@enigmax/primitives/react": "./use-marquee"
143
+ }
144
+ }
145
+ ],
146
+ "styles": true,
147
+ "themeHooks": [
148
+ "[data-tone=mono]",
149
+ ".logo-marquee",
150
+ ".logo-marquee.is-faded",
151
+ ".logo-marquee[data-grabbing]",
152
+ ".logo-marquee__item"
153
+ ],
154
+ "docs": "docs/notes/primitives.md#logo-marquee"
155
+ },
156
+ {
157
+ "name": "input",
158
+ "title": "Input, with what a password field needs",
159
+ "description": "A field you pass props to, with no styles of its own. A password gets its reveal toggle automatically; a generator, a strength meter and a breach check are one prop each and off until you ask. React renders every part of it, so nothing fights your tree.",
160
+ "targets": [
161
+ "vanilla",
162
+ "astro",
163
+ "react"
164
+ ],
165
+ "entry": {
166
+ "vanilla": "@enigmax/primitives",
167
+ "astro": "@enigmax/primitives",
168
+ "react": "@enigmax/primitives/react"
169
+ },
170
+ "exports": {
171
+ "vanilla": [
172
+ "createInput"
173
+ ],
174
+ "astro": [
175
+ "createInput"
176
+ ],
177
+ "react": [
178
+ "Input",
179
+ "PasswordStrength"
180
+ ]
181
+ },
182
+ "files": [
183
+ {
184
+ "path": "src/core/input.ts",
185
+ "dest": "input/input.ts",
186
+ "targets": [
187
+ "vanilla",
188
+ "astro",
189
+ "react"
190
+ ]
191
+ },
192
+ {
193
+ "path": "src/core/password.ts",
194
+ "dest": "input/password.ts",
195
+ "targets": [
196
+ "vanilla",
197
+ "astro",
198
+ "react"
199
+ ]
200
+ },
201
+ {
202
+ "path": "src/react/use-input.ts",
203
+ "dest": "input/use-input.ts",
204
+ "targets": [
205
+ "react"
206
+ ],
207
+ "rewrite": {
208
+ "@/core/input": "./input"
209
+ }
210
+ },
211
+ {
212
+ "path": "recipes/input/styles.css",
213
+ "dest": "input/styles.css",
214
+ "targets": [
215
+ "react",
216
+ "vanilla",
217
+ "astro"
218
+ ],
219
+ "style": "css"
220
+ },
221
+ {
222
+ "path": "src/react/input.tsx",
223
+ "dest": "input/index.tsx",
224
+ "main": true,
225
+ "targets": [
226
+ "react"
227
+ ],
228
+ "rewrite": {
229
+ "@/core/input": "./input",
230
+ "@/core/password": "./password"
231
+ }
232
+ },
233
+ {
234
+ "path": "recipes/input/tailwind.tsx",
235
+ "dest": "input/PasswordField.tsx",
236
+ "targets": [
237
+ "react"
238
+ ],
239
+ "style": "tailwind",
240
+ "rewrite": {
241
+ "@enigmax/primitives/react": "./index"
242
+ }
243
+ }
244
+ ],
245
+ "styles": false,
246
+ "themeHooks": [
247
+ "[data-enigma-input-root]",
248
+ "[data-enigma-input-root][data-revealed]",
249
+ "[data-enigma-input-root][data-breached]",
250
+ "[data-enigma-input-root][data-score]",
251
+ "[data-enigma-input-field]",
252
+ "[data-enigma-input]",
253
+ "[data-enigma-input][data-enigma-input-reveal=on]",
254
+ "[data-enigma-input-actions]",
255
+ "[data-enigma-input-actions][data-position=start]",
256
+ "[data-enigma-input-action]",
257
+ "[data-enigma-input-action=reveal]",
258
+ "[data-enigma-input-action=generate]",
259
+ "[data-enigma-password-strength]",
260
+ "[data-enigma-password-strength][data-empty]",
261
+ "[data-enigma-password-strength-track]",
262
+ "[data-enigma-password-strength-segment]",
263
+ "[data-enigma-password-strength-segment][data-filled]",
264
+ "[data-enigma-password-strength-label]",
265
+ "[data-enigma-password-strength-warning]"
266
+ ],
267
+ "docs": "docs/notes/primitives.md#input",
268
+ "recipes": [
269
+ "tailwind",
270
+ "css"
271
+ ]
272
+ },
273
+ {
274
+ "name": "search",
275
+ "title": "Search as you type",
276
+ "description": "Search as you type, fuzzy by default with Fuse.js. Debouncing, ranking and cancellation are the primitive's; the matching is replaceable, and dropping Fuse falls back to a built-in accent-insensitive substring matcher.",
277
+ "targets": [
278
+ "vanilla",
279
+ "astro",
280
+ "react"
281
+ ],
282
+ "entry": {
283
+ "vanilla": "@enigmax/primitives/search",
284
+ "astro": "@enigmax/primitives/search",
285
+ "react": "@enigmax/primitives/react"
286
+ },
287
+ "exports": {
288
+ "vanilla": [
289
+ "createSearch"
290
+ ],
291
+ "astro": [
292
+ "createSearch"
293
+ ],
294
+ "react": [
295
+ "useSearch"
296
+ ]
297
+ },
298
+ "files": [
299
+ {
300
+ "path": "src/core/search.ts",
301
+ "dest": "search/search.ts",
302
+ "targets": [
303
+ "vanilla",
304
+ "astro",
305
+ "react"
306
+ ]
307
+ },
308
+ {
309
+ "path": "src/react/use-search.ts",
310
+ "dest": "search/use-search.ts",
311
+ "targets": [
312
+ "react"
313
+ ],
314
+ "rewrite": {
315
+ "@/core/search": "./search"
316
+ },
317
+ "main": true
318
+ },
319
+ {
320
+ "path": "recipes/search/tailwind.tsx",
321
+ "dest": "search/Search.tsx",
322
+ "targets": [
323
+ "react"
324
+ ],
325
+ "style": "tailwind"
326
+ }
327
+ ],
328
+ "styles": false,
329
+ "themeHooks": [
330
+ "[data-enigma-search]"
331
+ ],
332
+ "optionalDependencies": {
333
+ "fuse.js": "Fuzzy matching. Omit it and a built-in substring matcher is used."
334
+ },
335
+ "docs": "docs/notes/primitives.md#search",
336
+ "recipes": [
337
+ "tailwind"
338
+ ],
339
+ "dependencies": {
340
+ "fuse.js": "^7.0.0"
341
+ }
342
+ },
343
+ {
344
+ "name": "button",
345
+ "title": "Button",
346
+ "description": "A button you pass props to. Disabled, loading, a keyboard shortcut and a cooldown that can outlive a reload collapse into one `available`; an href reports an anchor, so the framework's own Link stays your choice.",
347
+ "targets": [
348
+ "vanilla",
349
+ "astro",
350
+ "react"
351
+ ],
352
+ "entry": {
353
+ "vanilla": "@enigmax/primitives",
354
+ "astro": "@enigmax/primitives",
355
+ "react": "@enigmax/primitives/react"
356
+ },
357
+ "exports": {
358
+ "vanilla": [
359
+ "createButton"
360
+ ],
361
+ "astro": [
362
+ "createButton"
363
+ ],
364
+ "react": [
365
+ "Button",
366
+ "setLinkComponent",
367
+ "useButton"
368
+ ]
369
+ },
370
+ "files": [
371
+ {
372
+ "path": "src/core/button.ts",
373
+ "dest": "button/button.ts",
374
+ "targets": [
375
+ "vanilla",
376
+ "astro",
377
+ "react"
378
+ ]
379
+ },
380
+ {
381
+ "path": "src/react/use-button.ts",
382
+ "dest": "button/use-button.ts",
383
+ "targets": [
384
+ "react"
385
+ ],
386
+ "rewrite": {
387
+ "@/core/button": "./button"
388
+ }
389
+ },
390
+ {
391
+ "path": "src/react/link.ts",
392
+ "dest": "button/link.ts",
393
+ "targets": [
394
+ "react"
395
+ ]
396
+ },
397
+ {
398
+ "path": "src/react/button.tsx",
399
+ "dest": "button/index.tsx",
400
+ "main": true,
401
+ "targets": [
402
+ "react"
403
+ ],
404
+ "rewrite": {
405
+ "@/core/button": "./button",
406
+ "@/react/use-button": "./use-button",
407
+ "@/react/link": "./link"
408
+ }
409
+ }
410
+ ],
411
+ "styles": false,
412
+ "themeHooks": [
413
+ "[data-loading]",
414
+ "[data-cooldown]",
415
+ "[aria-disabled=true]",
416
+ "[aria-busy=true]"
417
+ ],
418
+ "docs": "docs/notes/primitives.md#button"
419
+ },
420
+ {
421
+ "name": "notifications",
422
+ "title": "Notification queue",
423
+ "description": "Ordering, dedupe by key, sticky errors and dismiss timers that hold instead of running while the tab is hidden. No rendering.",
424
+ "targets": [
425
+ "vanilla",
426
+ "astro",
427
+ "react"
428
+ ],
429
+ "entry": {
430
+ "vanilla": "@enigmax/primitives",
431
+ "astro": "@enigmax/primitives",
432
+ "react": "@enigmax/primitives/react"
433
+ },
434
+ "exports": {
435
+ "vanilla": [
436
+ "createNotifications"
437
+ ],
438
+ "astro": [
439
+ "createNotifications"
440
+ ],
441
+ "react": [
442
+ "useNotifications"
443
+ ]
444
+ },
445
+ "files": [
446
+ {
447
+ "path": "src/core/notifications.ts",
448
+ "dest": "notifications/notifications.ts",
449
+ "targets": [
450
+ "vanilla",
451
+ "astro",
452
+ "react"
453
+ ]
454
+ },
455
+ {
456
+ "path": "src/react/use-notifications.ts",
457
+ "dest": "notifications/use-notifications.ts",
458
+ "targets": [
459
+ "react"
460
+ ],
461
+ "rewrite": {
462
+ "@/core/notifications": "./notifications"
463
+ },
464
+ "main": true
465
+ },
466
+ {
467
+ "path": "recipes/notifications/tailwind.tsx",
468
+ "dest": "notifications/Notifications.tsx",
469
+ "targets": [
470
+ "react"
471
+ ],
472
+ "style": "tailwind"
473
+ }
474
+ ],
475
+ "styles": false,
476
+ "docs": "docs/notes/primitives.md#notifications",
477
+ "recipes": [
478
+ "tailwind"
479
+ ]
480
+ },
481
+ {
482
+ "name": "toast",
483
+ "title": "Toast",
484
+ "description": "The notification queue with a renderer on top: stacking, swipe to dismiss, a pointer that stops the clock, and a toast that is still on screen while it animates out. Ships the Ember theme, which is a stylesheet you own.",
485
+ "targets": [
486
+ "react"
487
+ ],
488
+ "entry": {
489
+ "react": "@enigmax/primitives/react"
490
+ },
491
+ "exports": {
492
+ "react": [
493
+ "Toaster",
494
+ "useNotifications"
495
+ ]
496
+ },
497
+ "files": [
498
+ {
499
+ "path": "src/core/notifications.ts",
500
+ "dest": "toast/notifications.ts",
501
+ "targets": [
502
+ "react"
503
+ ]
504
+ },
505
+ {
506
+ "path": "src/react/use-notifications.ts",
507
+ "dest": "toast/use-notifications.ts",
508
+ "targets": [
509
+ "react"
510
+ ],
511
+ "rewrite": {
512
+ "@/core/notifications": "./notifications"
513
+ }
514
+ },
515
+ {
516
+ "path": "src/react/toaster.tsx",
517
+ "dest": "toast/index.tsx",
518
+ "main": true,
519
+ "targets": [
520
+ "react"
521
+ ],
522
+ "rewrite": {
523
+ "@/core/notifications": "./notifications",
524
+ "@/react/use-notifications": "./use-notifications"
525
+ }
526
+ },
527
+ {
528
+ "path": "recipes/toast/styles.css",
529
+ "dest": "toast/styles.css",
530
+ "targets": [
531
+ "react"
532
+ ],
533
+ "style": "css"
534
+ },
535
+ {
536
+ "path": "recipes/toast/tailwind.tsx",
537
+ "dest": "toast/AppToaster.tsx",
538
+ "targets": [
539
+ "react"
540
+ ],
541
+ "style": "tailwind",
542
+ "rewrite": {
543
+ "@enigmax/utils/react": "./index"
544
+ }
545
+ }
546
+ ],
547
+ "styles": false,
548
+ "themeHooks": [
549
+ "[data-enigma-toaster]",
550
+ "[data-enigma-toaster][data-position]",
551
+ "[data-enigma-toast]",
552
+ "[data-enigma-toast][data-tone=error]",
553
+ "[data-enigma-toast][data-state=leaving]",
554
+ "[data-enigma-toast][data-swiping]",
555
+ "[data-enigma-toast][data-front]",
556
+ "[data-enigma-toast-title]",
557
+ "[data-enigma-toast-body]",
558
+ "[data-enigma-toast-action]",
559
+ "[data-enigma-toast-close]"
560
+ ],
561
+ "docs": "docs/notes/primitives.md#toast",
562
+ "recipes": [
563
+ "tailwind",
564
+ "css"
565
+ ]
566
+ },
567
+ {
568
+ "name": "network",
569
+ "title": "Connection state",
570
+ "description": "Online/offline plus what the Network Information API reports, and the recovery transition every hand-rolled version re-derives: `recovered` is false on a first load, so a page that opens online never announces a comeback that did not happen.",
571
+ "targets": [
572
+ "vanilla",
573
+ "astro",
574
+ "react"
575
+ ],
576
+ "entry": {
577
+ "vanilla": "@enigmax/primitives",
578
+ "astro": "@enigmax/primitives",
579
+ "react": "@enigmax/primitives/react"
580
+ },
581
+ "exports": {
582
+ "vanilla": [
583
+ "createNetworkMonitor"
584
+ ],
585
+ "astro": [
586
+ "createNetworkMonitor"
587
+ ],
588
+ "react": [
589
+ "useNetworkState"
590
+ ]
591
+ },
592
+ "files": [
593
+ {
594
+ "path": "src/core/network.ts",
595
+ "dest": "network/network.ts",
596
+ "targets": [
597
+ "vanilla",
598
+ "astro",
599
+ "react"
600
+ ]
601
+ },
602
+ {
603
+ "path": "src/react/use-network-state.ts",
604
+ "dest": "network/use-network-state.ts",
605
+ "targets": [
606
+ "react"
607
+ ],
608
+ "rewrite": {
609
+ "@/core/network": "./network"
610
+ },
611
+ "main": true
612
+ }
613
+ ],
614
+ "styles": false,
615
+ "docs": "docs/notes/primitives.md#network"
616
+ },
617
+ {
618
+ "name": "relative-time",
619
+ "title": "Relative timestamps",
620
+ "description": "\"3 hours ago\", kept current, localised by the page's own language. A timestamp with no zone is read as UTC instead of as the server's local time, and the absolute date is rendered as the element's child so a server render and a reader without JavaScript still see a real date.",
621
+ "targets": [
622
+ "vanilla",
623
+ "astro",
624
+ "react"
625
+ ],
626
+ "entry": {
627
+ "vanilla": "@enigmax/primitives",
628
+ "astro": "@enigmax/primitives",
629
+ "react": "@enigmax/primitives/react"
630
+ },
631
+ "exports": {
632
+ "vanilla": [
633
+ "relativeTimeView",
634
+ "relativeTimeAttributes",
635
+ "normalizeDate"
636
+ ],
637
+ "astro": [
638
+ "relativeTimeView",
639
+ "relativeTimeAttributes",
640
+ "normalizeDate"
641
+ ],
642
+ "react": [
643
+ "RelativeTime"
644
+ ]
645
+ },
646
+ "files": [
647
+ {
648
+ "path": "src/core/relative-time.ts",
649
+ "dest": "relative-time/relative-time.ts",
650
+ "targets": [
651
+ "vanilla",
652
+ "astro",
653
+ "react"
654
+ ]
655
+ },
656
+ {
657
+ "path": "src/react/relative-time.ts",
658
+ "dest": "relative-time/index.ts",
659
+ "targets": [
660
+ "react"
661
+ ],
662
+ "rewrite": {
663
+ "@/core/relative-time": "./relative-time"
664
+ }
665
+ },
666
+ {
667
+ "path": "recipes/relative-time/styles.css",
668
+ "dest": "relative-time/styles.css",
669
+ "targets": [
670
+ "vanilla",
671
+ "astro",
672
+ "react"
673
+ ]
674
+ }
675
+ ],
676
+ "styles": false,
677
+ "themeHooks": [
678
+ "relative-time",
679
+ "[data-relative-time-capitalize=true]",
680
+ "[data-relative-time-capitalize-first-letter=true]"
681
+ ],
682
+ "docs": "docs/notes/primitives.md#relative-time",
683
+ "dependencies": {
684
+ "@github/relative-time-element": "^4.4.0"
685
+ }
44
686
  }
45
687
  ]
46
688
  }
@@ -7,63 +7,103 @@
7
7
  "name": "cache",
8
8
  "title": "Short-TTL read cache",
9
9
  "description": "Read-through cache with in-flight deduplication, prefix invalidation and optional local/session persistence.",
10
- "targets": ["vanilla", "astro", "react"],
10
+ "targets": [
11
+ "vanilla",
12
+ "astro",
13
+ "react"
14
+ ],
11
15
  "entry": {
12
16
  "vanilla": "@enigmax/utils",
13
17
  "astro": "@enigmax/utils",
14
18
  "react": "@enigmax/utils/react"
15
19
  },
16
20
  "exports": {
17
- "vanilla": ["createCache"],
18
- "astro": ["createCache"],
19
- "react": ["useCached"]
21
+ "vanilla": [
22
+ "createCache"
23
+ ],
24
+ "astro": [
25
+ "createCache"
26
+ ],
27
+ "react": [
28
+ "useCached"
29
+ ]
20
30
  },
21
31
  "files": [
22
32
  {
23
33
  "path": "src/core/cache.ts",
24
- "dest": "cache.ts",
25
- "targets": ["vanilla", "astro", "react"]
34
+ "dest": "cache/cache.ts",
35
+ "targets": [
36
+ "vanilla",
37
+ "astro",
38
+ "react"
39
+ ]
26
40
  },
27
41
  {
28
42
  "path": "src/react/use-cached.ts",
29
- "dest": "use-cached.ts",
30
- "targets": ["react"],
31
- "rewrite": { "@/core/cache": "./cache" }
43
+ "dest": "cache/use-cached.ts",
44
+ "targets": [
45
+ "react"
46
+ ],
47
+ "rewrite": {
48
+ "@/core/cache": "./cache"
49
+ },
50
+ "main": true
32
51
  }
33
52
  ],
34
53
  "styles": false,
35
54
  "docs": "docs/notes/primitives.md#cache"
36
55
  },
37
56
  {
38
- "name": "notifications",
39
- "title": "Notification queue",
40
- "description": "Ordering, dedupe by key, sticky errors and dismiss timers that hold instead of running while the tab is hidden. No rendering.",
41
- "targets": ["vanilla", "astro", "react"],
57
+ "name": "password-breach",
58
+ "title": "Breached-password check",
59
+ "description": "Ask Have I Been Pwned whether a password has leaked, without sending it anywhere: it is hashed in the browser and only the first five characters of that hash are sent, so the service never learns which password was looked up. Reports a count and throws what it cannot do - what a breach means is the form's decision.",
60
+ "targets": [
61
+ "vanilla",
62
+ "astro",
63
+ "react"
64
+ ],
42
65
  "entry": {
43
66
  "vanilla": "@enigmax/utils",
44
67
  "astro": "@enigmax/utils",
45
- "react": "@enigmax/utils/react"
68
+ "react": "@enigmax/utils"
46
69
  },
47
70
  "exports": {
48
- "vanilla": ["createNotifications"],
49
- "astro": ["createNotifications"],
50
- "react": ["useNotifications"]
71
+ "vanilla": [
72
+ "checkPasswordBreach"
73
+ ],
74
+ "astro": [
75
+ "checkPasswordBreach"
76
+ ],
77
+ "react": [
78
+ "checkPasswordBreach"
79
+ ]
51
80
  },
52
81
  "files": [
53
82
  {
54
- "path": "src/core/notifications.ts",
55
- "dest": "notifications.ts",
56
- "targets": ["vanilla", "astro", "react"]
83
+ "path": "src/core/cache.ts",
84
+ "dest": "password-breach/cache.ts",
85
+ "targets": [
86
+ "vanilla",
87
+ "astro",
88
+ "react"
89
+ ]
57
90
  },
58
91
  {
59
- "path": "src/react/use-notifications.ts",
60
- "dest": "use-notifications.ts",
61
- "targets": ["react"],
62
- "rewrite": { "@/core/notifications": "./notifications" }
92
+ "path": "src/core/password-breach.ts",
93
+ "dest": "password-breach/index.ts",
94
+ "main": true,
95
+ "targets": [
96
+ "vanilla",
97
+ "astro",
98
+ "react"
99
+ ],
100
+ "rewrite": {
101
+ "@/core/cache": "./cache"
102
+ }
63
103
  }
64
104
  ],
65
105
  "styles": false,
66
- "docs": "docs/notes/primitives.md#notifications"
106
+ "docs": "docs/notes/primitives.md#password-breach"
67
107
  }
68
108
  ]
69
109
  }
@@ -7,6 +7,6 @@
7
7
  "minimalCode"
8
8
  ],
9
9
  "updated": "2026-07-17T00:17:02+02:00",
10
- "cliVersion": "1.35.10",
10
+ "cliVersion": "1.35.13",
11
11
  "sha": "3f0dcc28341bb0407860534f7ce9314cfc91b5e673f8f3d13b89d61851ed75f6"
12
12
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "On-demand over-engineering review - diff review, whole-repo audit, and enigma: debt-marker ledger (tags delete/stdlib/native/yagni/shrink, line/dep scoring); lists cuts, applies nothing.",
6
6
  "updated": "2026-06-16T11:24:30+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "f742a2be3f328b9ea1ff9a35a449177c2cbec35ad16e46f7054b7a873a2ab017"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Backend/API architecture: controller-service-repository layering, modern TypeScript project configuration (module resolution, strict flags, @/* path alias), API and request optimization (batching, avoiding redundant calls, skipping no-op writes), server-side caching (Redis), and Zod boundary validation.",
6
6
  "updated": "2026-08-02T19:59:01+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "652637f818b4ce4f4d5d83fa68fa57fe7842e32fb179523958c5496af54bb938"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Ciphera code style conventions (formatting, naming, imports incl. namespace imports for wide module surfaces, path-alias specifiers and no file extensions, comments, code-level anti-patterns; TypeScript-first, language-agnostic).",
6
6
  "updated": "2026-08-02T19:59:01+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "3beb0383a9cb0331ef8a579308a2517d24d57a8ba33751ad17d16d0a4f66e344"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Pre-delivery self-review gate, prioritized review dimensions, and change-quality criteria.",
6
6
  "updated": "2026-08-02T04:15:47+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "b35f0bb4a29f2346d9a1b00a4bfee9557a9b6a44116d9e6e59abcd945466dbcb"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Core engineering execution policy and harness orchestration (highest-authority rules), including the generalization rule that treats a named example as a class to sweep and fix.",
6
6
  "updated": "2026-08-02T04:28:51+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "95091678d4e72e3a9b1ef3ad0514e503a2a631f0d134bf772b4a1b3c66881995"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Senior database architecture policy: engine selection (PostgreSQL by default, SQLite only for local-first/embedded stores), ORM selection (Prisma in TypeScript/JavaScript), query optimization, anti-duplication/normalization, scalability, and RGPD/GDPR encryption.",
6
6
  "updated": "2026-08-04T18:33:27+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "26fddc14caf697e830448a2cc453ddc470fe83d098d50bd67b278effba63274e"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Reproduce-isolate-fix debugging methodology with root-cause discipline and regression verification.",
6
6
  "updated": "2026-08-02T04:15:47+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "968876bcac9ce6e5a20be05c89ac97191b112c9e08679a8dc506d150c375fec4"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Dependency and supply-chain security: lockfiles and reproducible installs, version pinning, vulnerability auditing, vetting/minimizing packages, vendoring, and SBOM/provenance.",
6
6
  "updated": "2026-06-01T00:45:28+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "6375d835c2aef2c9bd31ce116444dc3d796f510f9970a213aa3ac4696d7e21b9"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Transactional email: React Email templates instead of hand-written HTML tables, server-side rendering, one send module behind the provider SDK, plain-text alternatives, idempotent background sending, link safety, and deliverability (SPF/DKIM/DMARC, bounce suppression, unsubscribe).",
6
6
  "updated": "2026-07-30T19:29:19+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "c9724fdbcdbeab99573be3fd44d4cdd97c2a394d99f3c4395f118f17356b00ed"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Guidance for distinctive, intentional visual design when building new UI or reshaping an existing one.",
6
6
  "updated": "2026-07-29T01:18:36+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "9e30ee7d8a1a1e8c6e7f4e043857cd01841c68a427752e45bc0cad9ec5cfa279"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Frontend architecture: reusable components, abstraction thresholds, state management, real-time form validation where an emptied or not-yet-filled field is incomplete rather than invalid (a `*` on the label and an aria-disabled submit, never \"email is not valid\" over an empty input), no-op detection (skip any operation whose result equals current state, not just form saves; dirty means different from the loaded snapshot, not touched), instant first paint (render the shell, load data async, skeletons), perceived performance (prefetch on intent, debounce/throttle, cancel stale requests, avoid waterfalls, lazy widgets), large-list rendering (infinite scroll is the preferred default, pagination the deliberate exception when the design or the user calls for it, virtualization, skeletons, progressive loading), data views that ship their own affordances by default (a log, expense, transaction or history table is not done when the rows render: fuse.js search, the filters its column kinds imply, a date range, sort, filter state in the URL, and an export of the filtered set), every reference to an entity being a way into it (a name, id, project or path in a row links to that record, reveals it in a hover card, or at minimum copies and filters by it, never inert text; machine codes get human labels and raw payloads are never dumped into a cell), optimistic UI with rollback, visual restraint (one card level, spacing before borders, one elevation scale), icon actions (repeated row/card actions are icon-only buttons with aria-label plus title, not text labels), responsive/adaptive layout (fluid units, breakpoints, no overlap/overflow, viewport meta, touch targets), form fields that declare their keyboard and casing (autocapitalize/autocomplete/inputmode per field kind, set once in the shared Input, normalized on blur), variable-length text (min-width:0 in flex/grid, wrap vs truncate, long unbroken strings, worst-case content checks), auth screens (breached-password feedback, strength meter, cookie consent before login/register), and AI chat/agent interfaces via Vercel's AI Elements registry instead of hand-rolled message threads.",
6
6
  "updated": "2026-08-13T18:04:26+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "837f62ef43fa4ab46e7e74bb88dc6cb83e877f039565a80696e05345e28eb20d"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Git & contribution policy (senior engineering standards).",
6
6
  "updated": "2026-08-04T20:04:42+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "f55f4ed5474d5c6e7fd0804d88ac38fb3c9c3cd863519645d1415c02f4416db7"
9
9
  }
@@ -7,6 +7,6 @@
7
7
  "logoColorPolicy"
8
8
  ],
9
9
  "updated": "2026-07-17T00:17:02+02:00",
10
- "cliVersion": "1.35.10",
10
+ "cliVersion": "1.35.13",
11
11
  "sha": "09cdbefd98625b02a7d03685e5deed128238ff8454a83fe22279610fe3ef8ddf"
12
12
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Application and AI-agent security: secrets, authn/authz (least privilege), credential flows with breach-checked passwords (Have I Been Pwned), OWASP Top 10, transport/crypto baseline, cookies and consent, secure logging, and agent/MCP/tool-use safety.",
6
6
  "updated": "2026-08-02T19:59:01+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "3201b6d41437626eb7bf45648c0d7f8c419dc4de87fbd9d4ca83c5ae3b6b6edf"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Create new skills, modify and improve existing skills, and measure skill performance with evals and benchmarks.",
6
6
  "updated": "2026-08-03T00:16:54+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "a49f6166f57d734e0f13487332da37903e925f32257447856af0fbd6a5f1dc84"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Exhaustive completion discipline for long/multi-item tasks - inventory, coverage ledger, verified done.",
6
6
  "updated": "2026-08-02T04:15:47+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "4c2197954135dd3375f32d839238b82a95c0e93e976ddf5a9cbc4a0cf0146e97"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Concise, realistic technical copy - UI microcopy, descriptions, hints, empty/error states, and README/doc prose that informs without over-explaining or restating the obvious, and never uses a typographic dash.",
6
6
  "updated": "2026-07-28T20:30:23+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "a4b792103eb1f9dad93b9d70ea79dc18fe9cbbc318facf5adb47ae5907d842f9"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Test strategy, coverage gates, deterministic tests, mocking discipline, regression-first bug fixing, and test-suite organization (layout by type/domain, mirrored paths, file naming, fixture/helper placement).",
6
6
  "updated": "2026-06-16T17:11:49+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "3bdf591057b760f674fb2b1425f63acb426cda2c4f042e1a74c5a5d3807df664"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Strict frontend + backend schema validation, normalization before validation (shared normalizers: trim, lowercase email, capitalize names, canonicalize links and handles), real-time error timing (an emptied or not-yet-filled field is incomplete, not invalid: a `*` and an aria-disabled submit instead of \"not valid\" over an empty input), schema consistency, and safe client-facing error handling.",
6
6
  "updated": "2026-08-11T17:10:56+02:00",
7
- "cliVersion": "1.35.10",
7
+ "cliVersion": "1.35.13",
8
8
  "sha": "24928a4c9eed28ebede9f23f746b09a9d3d8176f8fcca61a7780a853fa5bed4e"
9
9
  }
@@ -1,6 +1,6 @@
1
1
  {
2
- "enigma-darwin-arm64": "728e35d28b2e8726b1db2994b17157c39c5caa370dd83c2848f58ca3c4a6d99c",
3
- "enigma-linux-arm64": "494ceda44c8cb913384dba6c38738aeba43662ce4a1a7128925539f26af81690",
4
- "enigma-linux-x64": "40bfdc690fd443d48d1657799b67e17ec5e704159cd48a19f3a0aa9767d06091",
5
- "enigma-win32-x64.exe": "e9538f2f23c8e777cc2d55e472c99a6c5f9a173028ebe2341e89ede68ff78803"
2
+ "enigma-darwin-arm64": "f117d39bb0bc4c83397200387e93b98f754afe392dccc3952b6eda3f7b8100d7",
3
+ "enigma-linux-arm64": "612d69fbf69e4c5b954c74e74e4cf848fa29fe6f59b040ed46629df1c629e5a0",
4
+ "enigma-linux-x64": "25b7cd9f1c28b4ed09946d1496446b17cc1a41c42154f4b3fd752c5af971e090",
5
+ "enigma-win32-x64.exe": "bc95cb8d5ec3dff23af0d845646e0b37b10bda2d3e9b29726440f78dccaa041f"
6
6
  }
@@ -329,6 +329,133 @@ var BUILTIN_RULES = [
329
329
  severity: "block",
330
330
  skill: "validation-policy"
331
331
  },
332
+ {
333
+ id: "fe-password-reveal-hand-rolled",
334
+ label: "Password reveal toggled by hand",
335
+ files: ["*.ts", "*.tsx", "*.js", "*.jsx", "*.astro", "*.vue", "*.svelte"],
336
+ excludeFiles: [
337
+ "*.test.*",
338
+ "*.spec.*",
339
+ "*.stories.*",
340
+ "*.min.js",
341
+ "**/tests/**",
342
+ "tests/**",
343
+ "**/__tests__/**",
344
+ "__tests__/**",
345
+ "**/dist/**",
346
+ "dist/**",
347
+ "**/build/**",
348
+ "build/**",
349
+ "**/node_modules/**",
350
+ "node_modules/**",
351
+ "**/vendor/**",
352
+ "vendor/**"
353
+ ],
354
+ scope: "file",
355
+ stage: "diff",
356
+ // The ternary that switches a field's type, in either order. Precise enough that
357
+ // it does not fire on anything else that mentions both words.
358
+ pattern: `["']password["']\\s*:\\s*["']text["']|["']text["']\\s*:\\s*["']password["']`,
359
+ absent: "@enigmax/primitives|useInput|createInput|<Input|enigma:allow-password-toggle",
360
+ message: 'A password reveal written by hand. Three things go wrong here and each has been measured: a `<button>` with no `type="button"` defaults to submit, so looking at your password posts the half-filled form; pressing the button pulls focus out of the field unless `mousedown` is prevented; and assigning `input.type` inside a click handler resets the caret to 0 in Chromium ONE MACROTASK later, so a restore that runs inline silently loses. `enigma add input` (@enigmax/primitives) is that behaviour with a test for each, and brings the generator, the strength meter and the breach check as props you can leave off. Mark the line `enigma:allow-password-toggle` to keep your own.',
361
+ severity: "warn",
362
+ skill: "frontend-policy"
363
+ },
364
+ {
365
+ id: "sec-generated-secret-math-random",
366
+ label: "A secret generated from Math.random",
367
+ files: ["*.ts", "*.tsx", "*.js", "*.jsx", "*.mjs", "*.cjs", "*.astro", "*.vue", "*.svelte"],
368
+ excludeFiles: [
369
+ "*.test.*",
370
+ "*.spec.*",
371
+ "*.stories.*",
372
+ "*.min.js",
373
+ "**/tests/**",
374
+ "tests/**",
375
+ "**/__tests__/**",
376
+ "__tests__/**",
377
+ "**/dist/**",
378
+ "dist/**",
379
+ "**/build/**",
380
+ "build/**",
381
+ "**/node_modules/**",
382
+ "node_modules/**",
383
+ "**/vendor/**",
384
+ "vendor/**"
385
+ ],
386
+ scope: "file",
387
+ // Both orders on one line: the word that says what is being made, and the call
388
+ // that makes it predictable.
389
+ pattern: "(?:password|passphrase|secret|token|otp|api[_-]?key|recovery[_-]?code|session[_-]?id)[^\\n]{0,80}Math\\.random|Math\\.random[^\\n]{0,80}(?:password|passphrase|secret|token|otp|api[_-]?key|recovery[_-]?code|session[_-]?id)",
390
+ absent: "getRandomValues|randomBytes|randomUUID|enigma:allow-insecure-random",
391
+ message: "A secret built from Math.random. It is a fast PRNG, not a CSPRNG: its state is recoverable from a handful of outputs, so anyone who sees one generated value can predict the next. Use `crypto.getRandomValues` in a browser or `crypto.randomBytes` in Node, and draw each index by REJECTION rather than `% alphabet.length`, which is biased because 2^32 is not a multiple of most alphabet sizes. `enigma add input` (@enigmax/primitives) exports `generatePassword` doing both, and throwing rather than falling back when no CSPRNG is available - a generator that quietly produces predictable secrets is worse than one that refuses, because nothing downstream can tell the difference. Mark the line `enigma:allow-insecure-random` where the value is genuinely not a secret.",
392
+ severity: "block",
393
+ skill: "security-policy"
394
+ },
395
+ {
396
+ id: "fe-clipboard-fallback-unchecked",
397
+ label: "Copy fallback whose result is thrown away",
398
+ files: ["*.ts", "*.tsx", "*.js", "*.jsx", "*.mjs", "*.astro", "*.vue", "*.svelte"],
399
+ excludeFiles: [
400
+ "*.test.*",
401
+ "*.spec.*",
402
+ "*.stories.*",
403
+ "*.min.js",
404
+ "**/tests/**",
405
+ "tests/**",
406
+ "**/__tests__/**",
407
+ "__tests__/**",
408
+ "**/dist/**",
409
+ "dist/**",
410
+ "**/build/**",
411
+ "build/**",
412
+ "**/node_modules/**",
413
+ "node_modules/**",
414
+ "**/vendor/**",
415
+ "vendor/**"
416
+ ],
417
+ scope: "file",
418
+ stage: "diff",
419
+ // A bare statement: the call is not assigned, returned, or tested. execCommand
420
+ // returns a boolean saying whether the copy happened, and this shape discards it.
421
+ pattern: `^[\\t ]*document\\.execCommand\\(["']copy["']\\)\\s*;`,
422
+ absent: "enigma:allow-unchecked-copy",
423
+ message: 'The clipboard fallback\'s result is discarded. `document.execCommand("copy")` RETURNS whether the copy happened - it fails silently under a clipboard-guard extension, in a cross-origin frame, and without a user gesture - so a button that flashes a tick after calling it is telling the reader something it never checked, and they find out on paste. Test the return value and show the confirmation only when it is true. While you are there: give the scratch textarea `position: fixed` and `opacity: 0`, because appending a visible one scrolls the page to the bottom on every copy. Mark the line `enigma:allow-unchecked-copy` where the outcome genuinely does not matter (frontend-policy).',
424
+ severity: "warn",
425
+ skill: "frontend-policy"
426
+ },
427
+ {
428
+ id: "fe-relative-time-hand-rolled",
429
+ label: "Relative time computed by hand",
430
+ files: ["*.ts", "*.tsx", "*.js", "*.jsx", "*.astro", "*.vue", "*.svelte"],
431
+ excludeFiles: [
432
+ "*.test.*",
433
+ "*.spec.*",
434
+ "*.stories.*",
435
+ "*.min.js",
436
+ "**/tests/**",
437
+ "tests/**",
438
+ "**/__tests__/**",
439
+ "__tests__/**",
440
+ "**/dist/**",
441
+ "dist/**",
442
+ "**/build/**",
443
+ "build/**",
444
+ "**/node_modules/**",
445
+ "node_modules/**",
446
+ "**/vendor/**",
447
+ "vendor/**"
448
+ ],
449
+ scope: "file",
450
+ stage: "diff",
451
+ // A millisecond difference divided into units - the shape every hand-rolled
452
+ // "3 hours ago" has, and one a real formatter never needs.
453
+ pattern: "(?:Date\\.now\\(\\)|getTime\\(\\))[^\\n]{0,60}-[^\\n]{0,60}\\/\\s*(?:1000|60000|3600000|86400000|1000\\s*\\*)",
454
+ absent: "@enigmax/utils|RelativeTime|relativeTimeView|relative-time-element|Intl\\.RelativeTimeFormat|date-fns|dayjs|luxon|moment|enigma:allow-manual-relative-time",
455
+ message: '"3 hours ago" computed from a millisecond difference. Three things this misses: it is English-only, where `Intl.RelativeTimeFormat` speaks the reader\'s language; it renders once and then goes stale, so a page left open says "3 hours ago" tomorrow; and a timestamp with no zone (`2026-08-13 22:41:00` out of a date column) is read as LOCAL time by `new Date()`, which puts every reader away from the server hours out - silently, because a wrong time is still a valid one. `enigma add relative-time` (@enigmax/utils) handles all three and renders the absolute date as a fallback, so a server render and a reader without JavaScript still see a real date. Mark the line `enigma:allow-manual-relative-time` when the arithmetic is the point.',
456
+ severity: "warn",
457
+ skill: "frontend-policy"
458
+ },
332
459
  {
333
460
  id: "sec-password-breach-check",
334
461
  label: "A new password is checked against the breach corpus",
@@ -360,7 +487,7 @@ var BUILTIN_RULES = [
360
487
  // it, including a call into a shared hook whose name carries `pwned`/`breach`.
361
488
  pattern: `autocomplete=\\{?["']new-password["']`,
362
489
  absent: "pwnedpasswords|haveibeenpwned|hibp|pwned|breach|enigma:allow-no-breach-check",
363
- message: "A password is created here with no breach check. Length and symbol rules do not stop a password that is already in a credential-stuffing list. Check it against Have I Been Pwned's Pwned Passwords range API - free, no key, and the password never leaves the client: SHA-1 it, uppercase the hex, GET https://api.pwnedpasswords.com/range/<first 5 chars> with `Add-Padding: true`, and look for the remaining 35 characters in the `SUFFIX:COUNT` lines. Debounce it as the user types, abort the in-flight request when the value changes, repeat the check server-side on submit, and fail OPEN if the lookup errors so an outage never blocks a signup. For a flow that genuinely cannot reach it, add an `enigma:allow-no-breach-check` note (security-policy).",
490
+ message: "A password is created here with no breach check. Length and symbol rules do not stop a password that is already in a credential-stuffing list. Check it against Have I Been Pwned's Pwned Passwords range API - free, no key, and the password never leaves the client: SHA-1 it, uppercase the hex, GET https://api.pwnedpasswords.com/range/<first 5 chars> with `Add-Padding: true`, and look for the remaining 35 characters in the `SUFFIX:COUNT` lines. `enigma add password-breach` (@enigmax/utils) is exactly that call, with the padding header, the decoy entries rejected and the range responses cached. Debounce it as the user types, abort the in-flight request when the value changes, repeat the check server-side on submit, and fail OPEN if the lookup errors so an outage never blocks a signup. For a flow that genuinely cannot reach it, add an `enigma:allow-no-breach-check` note (security-policy).",
364
491
  severity: "block",
365
492
  skill: "security-policy"
366
493
  },
@@ -400,7 +527,7 @@ var BUILTIN_RULES = [
400
527
  // of an equality/containment operator.
401
528
  pattern: `autocomplete=\\{?["']new-password["']`,
402
529
  absent: "userInputs|user_inputs|UserAttributeSimilarity|sameAs(?:Email|Username|Identity)|matchesIdentity|containsIdentity|identityMatch|notIdentity|personalInfo|(?:password|passwd|pwd)[^\\n]{0,60}(?:===|==|!==|\\.includes\\(|\\.indexOf\\(|\\.startsWith\\(|localeCompare)[^\\n]{0,60}(?:email|username|user_?name|handle)|(?:email|username|user_?name|handle)[^\\n]{0,60}(?:===|==|!==|\\.includes\\(|\\.indexOf\\(|\\.startsWith\\(|localeCompare)[^\\n]{0,60}(?:password|passwd|pwd)|enigma:allow-identity-password",
403
- message: "A password is created here with nothing stopping it from being the account's own identity. `Fjrg2007` for the user `fjrg2007` is one guess for anyone who knows the email address. Refuse a candidate that equals, contains (4 characters or more), or closely resembles the email, its local part, the username, the display name or the site name - comparing NORMALIZED values on both sides (lowercase, trim, NFKD then strip accents, drop everything that is not a letter or a digit), so `F.J.R.G_2007` and `fjrg2007` are the same string and casing is never a difference. Declare it on the OBJECT schema, since a password field cannot see the email beside it, and run it again on the server where the real identity lives. A strength meter fed `userInputs` scores this badly but is advisory - keep the refusal as its own rule. For a flow with no identity to compare against, add an `enigma:allow-identity-password` note (security-policy, validation-policy).",
530
+ message: "A password is created here with nothing stopping it from being the account's own identity. `Fjrg2007` for the user `fjrg2007` is one guess for anyone who knows the email address. Refuse a candidate that equals, contains (4 characters or more), or closely resembles the email, its local part, the username, the display name or the site name - comparing NORMALIZED values on both sides (lowercase, trim, NFKD then strip accents, drop everything that is not a letter or a digit), so `F.J.R.G_2007` and `fjrg2007` are the same string and casing is never a difference. Declare it on the OBJECT schema, since a password field cannot see the email beside it, and run it again on the server where the real identity lives. A strength meter fed `userInputs` scores this badly but is advisory - keep the refusal as its own rule; `enigma add input` (@enigmax/primitives) takes `strength={{ userInputs }}` and reports it. For a flow with no identity to compare against, add an `enigma:allow-identity-password` note (security-policy, validation-policy).",
404
531
  severity: "block",
405
532
  skill: "security-policy"
406
533
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enigma-cli",
3
- "version": "1.35.10",
3
+ "version": "1.35.13",
4
4
  "description": "Everything you need to work with a coding agent: install shared policy skills for Claude Code, OpenAI Codex and opencode, and set up portable git security hooks.",
5
5
  "type": "module",
6
6
  "bin": {