enigma-cli 1.42.1 → 1.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/registry/primitives/registry.json +498 -8
- package/assets/registry/utils/registry.json +4 -2
- package/assets/skills/anti-overengineering-policy/skill.json +1 -1
- package/assets/skills/anti-overengineering-review/skill.json +1 -1
- package/assets/skills/backend-policy/skill.json +1 -1
- package/assets/skills/ciphera-style-policy/skill.json +1 -1
- package/assets/skills/code-review-policy/skill.json +1 -1
- package/assets/skills/core-engineering-policy/skill.json +1 -1
- package/assets/skills/database-expert/skill.json +1 -1
- package/assets/skills/debugging-policy/skill.json +1 -1
- package/assets/skills/dependency-policy/skill.json +1 -1
- package/assets/skills/email-policy/skill.json +1 -1
- package/assets/skills/frontend-design/skill.json +1 -1
- package/assets/skills/frontend-policy/SKILL.md +9 -1
- package/assets/skills/frontend-policy/skill.json +3 -3
- package/assets/skills/git-policy/skill.json +1 -1
- package/assets/skills/logo-sourcing-policy/skill.json +1 -1
- package/assets/skills/security-policy/SKILL.md +9 -0
- package/assets/skills/security-policy/skill.json +3 -3
- package/assets/skills/skill-creator/skill.json +1 -1
- package/assets/skills/task-completion-policy/skill.json +1 -1
- package/assets/skills/technical-writing-policy/skill.json +1 -1
- package/assets/skills/testing-policy/skill.json +1 -1
- package/assets/skills/validation-policy/skill.json +1 -1
- package/bin/checksums.json +4 -4
- package/bin/enigma.mjs +34 -0
- package/dist/codegraph-hook.js +2483 -0
- package/dist/guardrails.js +282 -1
- package/dist/post-edit.js +283 -2
- package/package.json +1 -1
package/dist/guardrails.js
CHANGED
|
@@ -1063,6 +1063,168 @@ var BUILTIN_RULES = [
|
|
|
1063
1063
|
severity: "block",
|
|
1064
1064
|
skill: "frontend-policy"
|
|
1065
1065
|
},
|
|
1066
|
+
{
|
|
1067
|
+
id: "fe-lightbox-hand-rolled",
|
|
1068
|
+
label: "Image viewers come from the primitive",
|
|
1069
|
+
files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.ts", "*.js"],
|
|
1070
|
+
excludeFiles: [
|
|
1071
|
+
"*.test.*",
|
|
1072
|
+
"*.spec.*",
|
|
1073
|
+
"**/tests/**",
|
|
1074
|
+
"**/__tests__/**",
|
|
1075
|
+
"**/fixtures/**",
|
|
1076
|
+
"*.min.js",
|
|
1077
|
+
"**/dist/**",
|
|
1078
|
+
"**/build/**",
|
|
1079
|
+
"**/node_modules/**",
|
|
1080
|
+
"**/vendor/**",
|
|
1081
|
+
"dist/**",
|
|
1082
|
+
"build/**",
|
|
1083
|
+
"node_modules/**",
|
|
1084
|
+
"vendor/**"
|
|
1085
|
+
],
|
|
1086
|
+
scope: "file",
|
|
1087
|
+
stage: "diff",
|
|
1088
|
+
fileCheck: "fe-lightbox-hand-rolled",
|
|
1089
|
+
message: "A lightbox written by hand: a picture in a dialog, a wheel that scales it, and a zoom kept as state. The tail is long and always the same. A wheel listener is PASSIVE by default, so every notch zooms the image and scrolls the article behind the backdrop with it. Scaling around the frame's centre slides whatever was under the pointer away from it, so zooming into a face means hunting for it again. Nothing bounds the pan, so a drag strands the picture off screen with no way back but closing. The dialog is left in the page rather than portalled, and the first ancestor with `overflow: hidden` clips it. Escape does not close it, the body still scrolls under it, and focus never returns to the image it was opened from. `enigma add image` (@enigmax/primitives) is `<Image>`: a press enlarges it and the wheel zooms around the cursor by default, with a drag to pan, a pinch on touch and the keyboard driving both - and the arrows, the strip of previews, a download menu and a discard action off until asked for. The viewer is its own chunk, fetched on intent, so a page of images nobody enlarges downloads none of it. Mark the line `enigma:allow-hand-rolled-lightbox` when the viewer genuinely has to be your own (frontend-policy).",
|
|
1090
|
+
severity: "block",
|
|
1091
|
+
skill: "frontend-policy"
|
|
1092
|
+
},
|
|
1093
|
+
{
|
|
1094
|
+
id: "fe-video-player-hand-rolled",
|
|
1095
|
+
label: "Video controls come from the primitive",
|
|
1096
|
+
files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.ts", "*.js"],
|
|
1097
|
+
excludeFiles: [
|
|
1098
|
+
"*.test.*",
|
|
1099
|
+
"*.spec.*",
|
|
1100
|
+
"**/tests/**",
|
|
1101
|
+
"**/__tests__/**",
|
|
1102
|
+
"**/fixtures/**",
|
|
1103
|
+
"*.min.js",
|
|
1104
|
+
"**/dist/**",
|
|
1105
|
+
"**/build/**",
|
|
1106
|
+
"**/node_modules/**",
|
|
1107
|
+
"**/vendor/**",
|
|
1108
|
+
"dist/**",
|
|
1109
|
+
"build/**",
|
|
1110
|
+
"node_modules/**",
|
|
1111
|
+
"vendor/**"
|
|
1112
|
+
],
|
|
1113
|
+
scope: "file",
|
|
1114
|
+
stage: "diff",
|
|
1115
|
+
fileCheck: "fe-video-player-hand-rolled",
|
|
1116
|
+
message: "Controls written by hand over a `<video>`. What follows is the same every time: `play()` returns a promise that REJECTS - no gesture yet, an autoplay policy, no source - and the unhandled rejection leaves the button saying pause over a video that never started; the state is kept in the component instead of read back from the element's own events, so anything else that plays or pauses it (the media keys, picture in picture, the operating system) desynchronises the bar; the scrubber is a div with a click handler rather than a slider a keyboard can drive or a screen reader can read; the buffered ranges are ignored, or read as `buffered.end(0)`, which reports a range nobody is watching; the clock changes width as it passes ten minutes and shoves every control along with it; the shortcuts fire while somebody is typing in a comment box; and fullscreen calls `requestFullscreen` on a container, which iOS Safari does not have at all. `enigma add video` (@enigmax/primitives) is `<Video>`, shaped after Plyr: the same control set and the same shortcuts, the element as the source of truth, and a theme restyled through `data-*` attributes. Mark the line `enigma:allow-hand-rolled-video-player` when the player genuinely has to be your own (frontend-policy).",
|
|
1117
|
+
severity: "block",
|
|
1118
|
+
skill: "frontend-policy"
|
|
1119
|
+
},
|
|
1120
|
+
{
|
|
1121
|
+
id: "sec-2fa-reauth-prompt",
|
|
1122
|
+
label: "A credential just proven is not asked for again",
|
|
1123
|
+
files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.ts", "*.js", "*.py", "*.rb", "*.php"],
|
|
1124
|
+
excludeFiles: [
|
|
1125
|
+
"*.test.*",
|
|
1126
|
+
"*.spec.*",
|
|
1127
|
+
"**/tests/**",
|
|
1128
|
+
"**/__tests__/**",
|
|
1129
|
+
"**/fixtures/**",
|
|
1130
|
+
"*.min.js",
|
|
1131
|
+
"**/dist/**",
|
|
1132
|
+
"**/build/**",
|
|
1133
|
+
"**/node_modules/**",
|
|
1134
|
+
"**/vendor/**",
|
|
1135
|
+
"dist/**",
|
|
1136
|
+
"build/**",
|
|
1137
|
+
"node_modules/**",
|
|
1138
|
+
"vendor/**"
|
|
1139
|
+
],
|
|
1140
|
+
scope: "file",
|
|
1141
|
+
stage: "diff",
|
|
1142
|
+
fileCheck: "sec-2fa-reauth-prompt",
|
|
1143
|
+
message: "Turning 2FA on asks for the password the visitor typed a moment ago. Nothing is verified by asking: the session was established by that exact credential during sign-up or sign-in, so the answer is already known - and what the prompt teaches is that a password box can appear at any moment for no stated reason, which is the habit every phishing page relies on. The step that proves the factor works is the CODE from the authenticator, not the password. Re-authentication is a real control and this is not it: it belongs to a sensitive change on a session that is no longer FRESH, so record `authenticatedAt` when the session is created and treat it as fresh for 5-15 minutes; ask again only past that window, and prefer the second factor or a passkey over the password when you do. The actions worth guarding that way are changing the password or the email, disabling 2FA or removing a factor, regenerating recovery codes, issuing an API token, changing payout details and deleting the account. Mark the line `enigma:allow-2fa-password-prompt` where a policy genuinely requires the prompt (security-policy).",
|
|
1144
|
+
severity: "block",
|
|
1145
|
+
skill: "security-policy"
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
id: "fe-select-hand-rolled",
|
|
1149
|
+
label: "Custom selects come from the primitive",
|
|
1150
|
+
files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.ts", "*.js"],
|
|
1151
|
+
excludeFiles: [
|
|
1152
|
+
"*.test.*",
|
|
1153
|
+
"*.spec.*",
|
|
1154
|
+
"**/tests/**",
|
|
1155
|
+
"**/__tests__/**",
|
|
1156
|
+
"**/fixtures/**",
|
|
1157
|
+
"*.min.js",
|
|
1158
|
+
"**/dist/**",
|
|
1159
|
+
"**/build/**",
|
|
1160
|
+
"**/node_modules/**",
|
|
1161
|
+
"**/vendor/**",
|
|
1162
|
+
"dist/**",
|
|
1163
|
+
"build/**",
|
|
1164
|
+
"node_modules/**",
|
|
1165
|
+
"vendor/**"
|
|
1166
|
+
],
|
|
1167
|
+
scope: "file",
|
|
1168
|
+
stage: "diff",
|
|
1169
|
+
fileCheck: "fe-select-hand-rolled",
|
|
1170
|
+
message: "A select written by hand. Replacing the native one is often right - it cannot hold an icon, a second line or a tag, and its popup is drawn by the OS - but what replaces it has to do everything the platform was doing for free, and the panel is the part that gets written. Missing every time: a typeahead (one letter walks the rows that start with it, a longer buffer refines), a disabled row that is listed and announced but that the arrow keys walk PAST rather than onto, a highlight that the pointer and the keyboard share (two highlights on screen is what makes Enter open the row the mouse is not on), a panel that measures before it opens instead of hanging off the bottom of the window, a hidden input so the form it sits in can actually submit the value, and a long list rendered a window at a time instead of 250 rows for the seven anybody sees. `enigma add select` (@enigmax/primitives) is all of it with a test each, plus a filter that turns itself on from the option count, multi-select with tags, and a baseline theme because an unstyled popup is transparent text lying on the page. Mark the line `enigma:allow-hand-rolled-select` when the listbox genuinely has to be your own (frontend-policy).",
|
|
1171
|
+
severity: "block",
|
|
1172
|
+
skill: "frontend-policy"
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
id: "fe-toast-hand-rolled",
|
|
1176
|
+
label: "Toasts come from the primitive",
|
|
1177
|
+
files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.ts", "*.js"],
|
|
1178
|
+
excludeFiles: [
|
|
1179
|
+
"*.test.*",
|
|
1180
|
+
"*.spec.*",
|
|
1181
|
+
"**/tests/**",
|
|
1182
|
+
"**/__tests__/**",
|
|
1183
|
+
"**/fixtures/**",
|
|
1184
|
+
"*.min.js",
|
|
1185
|
+
"**/dist/**",
|
|
1186
|
+
"**/build/**",
|
|
1187
|
+
"**/node_modules/**",
|
|
1188
|
+
"**/vendor/**",
|
|
1189
|
+
"dist/**",
|
|
1190
|
+
"build/**",
|
|
1191
|
+
"node_modules/**",
|
|
1192
|
+
"vendor/**"
|
|
1193
|
+
],
|
|
1194
|
+
scope: "file",
|
|
1195
|
+
stage: "diff",
|
|
1196
|
+
fileCheck: "fe-toast-hand-rolled",
|
|
1197
|
+
message: "A toast stack written by hand: an array of messages and a `setTimeout` that removes them. The queue is the easy half; what is missing is the timing. A timer that does not PAUSE while the pointer is over the toast dismisses the one thing somebody stopped to read, and one that does not pause on a hidden tab expires every toast the moment the tab is left. A timer restarted by a re-render never fires at all, and one keyed on the array index dismisses the wrong toast as soon as two overlap. Then the stack pushes the page around as it grows, and none of it is announced, because a toast needs a live region rather than a div that appeared. `enigma add toast` (@enigmax/primitives) is the real thing - hover and focus pause it, the tab pauses it, promise toasts, actions, and one styled surface you can restyle. Mark the line `enigma:allow-hand-rolled-toast` when the stack genuinely has to be your own (frontend-policy).",
|
|
1198
|
+
severity: "block",
|
|
1199
|
+
skill: "frontend-policy"
|
|
1200
|
+
},
|
|
1201
|
+
{
|
|
1202
|
+
id: "fe-palette-hand-rolled",
|
|
1203
|
+
label: "The Ctrl+K palette comes from the primitive",
|
|
1204
|
+
files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.ts", "*.js"],
|
|
1205
|
+
excludeFiles: [
|
|
1206
|
+
"*.test.*",
|
|
1207
|
+
"*.spec.*",
|
|
1208
|
+
"**/tests/**",
|
|
1209
|
+
"**/__tests__/**",
|
|
1210
|
+
"**/fixtures/**",
|
|
1211
|
+
"*.min.js",
|
|
1212
|
+
"**/dist/**",
|
|
1213
|
+
"**/build/**",
|
|
1214
|
+
"**/node_modules/**",
|
|
1215
|
+
"**/vendor/**",
|
|
1216
|
+
"dist/**",
|
|
1217
|
+
"build/**",
|
|
1218
|
+
"node_modules/**",
|
|
1219
|
+
"vendor/**"
|
|
1220
|
+
],
|
|
1221
|
+
scope: "file",
|
|
1222
|
+
stage: "diff",
|
|
1223
|
+
fileCheck: "fe-palette-hand-rolled",
|
|
1224
|
+
message: "A command palette written by hand. The shortcut and the panel are an afternoon; the list underneath is where every palette breaks. A shorter result list leaves the highlight past the end, so Enter opens nothing - the single most common palette bug. The arrows stop at the last row instead of wrapping, which reads as a frozen panel. Focus moves to the row, so the caret leaves the field and a screen reader hears nothing (a row is announced through `aria-activedescendant`, with focus never leaving the input). The pointer gets its own highlight beside the keyboard's, so Enter opens the row the mouse is not on. It reopens holding the last query, which hides what an empty query is for - the recents. And dismissing hands focus to the top of the document instead of back to the trigger. `enigma add palette` (@enigmax/primitives) has each of those with a test, plus grouping whose keyboard sequence stays flat across groups, recents guarded against a storage that throws in a private window, and a scroll lock that compensates for the scrollbar. Mark the line `enigma:allow-hand-rolled-palette` when the palette genuinely has to be your own (frontend-policy).",
|
|
1225
|
+
severity: "block",
|
|
1226
|
+
skill: "frontend-policy"
|
|
1227
|
+
},
|
|
1066
1228
|
{
|
|
1067
1229
|
id: "fe-icon-action-button",
|
|
1068
1230
|
label: "Repeated actions are icon buttons, not text labels",
|
|
@@ -1862,7 +2024,13 @@ var FILE_CHECKS = {
|
|
|
1862
2024
|
"fe-new-password-affordance-on-signin": (content) => newPasswordAffordanceOnSignIn(content),
|
|
1863
2025
|
"fe-context-menu-hand-rolled": (content) => handRolledContextMenu(content),
|
|
1864
2026
|
"fe-selection-hand-rolled": (content) => handRolledSelection(content),
|
|
1865
|
-
"fe-color-picker-hand-rolled": (content) => handRolledColorPicker(content)
|
|
2027
|
+
"fe-color-picker-hand-rolled": (content) => handRolledColorPicker(content),
|
|
2028
|
+
"fe-lightbox-hand-rolled": (content) => handRolledLightbox(content),
|
|
2029
|
+
"fe-video-player-hand-rolled": (content) => handRolledVideoPlayer(content),
|
|
2030
|
+
"sec-2fa-reauth-prompt": (content) => twoFactorPasswordPrompt(content),
|
|
2031
|
+
"fe-select-hand-rolled": (content) => handRolledSelect(content),
|
|
2032
|
+
"fe-toast-hand-rolled": (content) => handRolledToast(content),
|
|
2033
|
+
"fe-palette-hand-rolled": (content) => handRolledPalette(content)
|
|
1866
2034
|
};
|
|
1867
2035
|
var FIXERS = {
|
|
1868
2036
|
"fe-name-input-capitalize": (line, file) => {
|
|
@@ -2168,6 +2336,113 @@ function handRolledColorPicker(content) {
|
|
|
2168
2336
|
}
|
|
2169
2337
|
return [];
|
|
2170
2338
|
}
|
|
2339
|
+
var LIGHTBOX_IMAGE = /<img\b|<Image\b|createElement\(\s*["'`]img["'`]/;
|
|
2340
|
+
var LIGHTBOX_WHEEL = /onWheel|@wheel|on:wheel|addEventListener\(\s*["'`]wheel["'`]/;
|
|
2341
|
+
var LIGHTBOX_ZOOM = /\bset(?:Scale|Zoom)\b|\b(?:scale|zoom)\s*[:=]\s*[0-9(]|scale\(/;
|
|
2342
|
+
var LIGHTBOX_DIALOG = /role=["']dialog["']|aria-modal|lightbox|createPortal|["']Escape["']|backdrop/i;
|
|
2343
|
+
var LIGHTBOX_MITIGATED = /@enigmax\/primitives|data-enigma-image|ImageViewer\b|yet-another-react-lightbox|react-image-lightbox|photoswipe|medium-zoom|viewerjs|fancybox|lightgallery|react-medium-image-zoom|enigma:allow-hand-rolled-lightbox/i;
|
|
2344
|
+
function handRolledLightbox(content) {
|
|
2345
|
+
if (LIGHTBOX_MITIGATED.test(content)) return [];
|
|
2346
|
+
if (!LIGHTBOX_IMAGE.test(content) || !LIGHTBOX_WHEEL.test(content)) return [];
|
|
2347
|
+
if (!LIGHTBOX_ZOOM.test(content) || !LIGHTBOX_DIALOG.test(content)) return [];
|
|
2348
|
+
const lines = content.split("\n");
|
|
2349
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2350
|
+
const line = lines[i];
|
|
2351
|
+
if (COMMENT_LINE.test(line) || /enigma:/.test(line)) continue;
|
|
2352
|
+
if (!LIGHTBOX_WHEEL.test(line)) continue;
|
|
2353
|
+
return [{ line: i + 1, detail: "a picture in a dialog, a wheel that scales it, and the zoom it keeps" }];
|
|
2354
|
+
}
|
|
2355
|
+
return [];
|
|
2356
|
+
}
|
|
2357
|
+
var VIDEO_ELEMENT = /<video\b|createElement\(\s*["'`]video["'`]|HTMLVideoElement/;
|
|
2358
|
+
var VIDEO_DRIVEN = /\.play\(\)|\.pause\(\)|\bplaybackRate\b|requestPictureInPicture/;
|
|
2359
|
+
var VIDEO_TIMELINE = /\bcurrentTime\b|timeupdate|\.buffered\b/i;
|
|
2360
|
+
var VIDEO_CONTROLS = /aria-label=["'](?:Play|Pause|Mute|Seek|Fullscreen)|onClick=\{[^}]*(?:play|pause)|requestFullscreen|toggleFullscreen|isPlaying|setPlaying/i;
|
|
2361
|
+
var VIDEO_MITIGATED = /@enigmax\/primitives|data-enigma-video|["'`]plyr|from\s+["'`]plyr|video\.js|videojs|react-player|vidstack|media-chrome|@vime|shaka-player|enigma:allow-hand-rolled-video-player/i;
|
|
2362
|
+
function handRolledVideoPlayer(content) {
|
|
2363
|
+
if (VIDEO_MITIGATED.test(content)) return [];
|
|
2364
|
+
if (!VIDEO_ELEMENT.test(content) || !VIDEO_DRIVEN.test(content)) return [];
|
|
2365
|
+
if (!VIDEO_TIMELINE.test(content) || !VIDEO_CONTROLS.test(content)) return [];
|
|
2366
|
+
const lines = content.split("\n");
|
|
2367
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2368
|
+
const line = lines[i];
|
|
2369
|
+
if (COMMENT_LINE.test(line) || /enigma:/.test(line)) continue;
|
|
2370
|
+
if (!VIDEO_DRIVEN.test(line)) continue;
|
|
2371
|
+
return [{ line: i + 1, detail: "a video element, its playback driven by code, and a bar of controls over it" }];
|
|
2372
|
+
}
|
|
2373
|
+
return [];
|
|
2374
|
+
}
|
|
2375
|
+
var TWOFA_SUBJECT = /2fa|two[-_ ]?factor|totp|mfa|authenticator|otpauth/i;
|
|
2376
|
+
var TWOFA_ENROLL = /enroll?(?!ed)|set[-_ ]?up|activate(?!d)|enable(?!d)|provision(?!ed)/i;
|
|
2377
|
+
var TWOFA_PASSWORD = /type=["']password["']|\bpassword\s*[:=]|\bpassword\b\s*[,)]|currentPassword|current_password/i;
|
|
2378
|
+
var TWOFA_MITIGATED = /authenticatedAt|authenticated_at|reauthenticat|sudo[-_ ]?mode|sudoMode|freshSession|fresh_session|requireFresh|lastAuthAt|last_auth_at|recentlyAuthenticated|stepUp|step_up|enigma:allow-2fa-password-prompt/i;
|
|
2379
|
+
var TWOFA_WINDOW = 25;
|
|
2380
|
+
function twoFactorPasswordPrompt(content) {
|
|
2381
|
+
if (TWOFA_MITIGATED.test(content)) return [];
|
|
2382
|
+
if (!TWOFA_SUBJECT.test(content) || !TWOFA_ENROLL.test(content)) return [];
|
|
2383
|
+
const lines = content.split("\n");
|
|
2384
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2385
|
+
const line = lines[i];
|
|
2386
|
+
if (COMMENT_LINE.test(line) || /enigma:/.test(line)) continue;
|
|
2387
|
+
if (!TWOFA_SUBJECT.test(line) || !TWOFA_ENROLL.test(line)) continue;
|
|
2388
|
+
for (let j = Math.max(0, i - TWOFA_WINDOW); j < Math.min(lines.length, i + TWOFA_WINDOW); j++) {
|
|
2389
|
+
const near = lines[j];
|
|
2390
|
+
if (COMMENT_LINE.test(near) || /enigma:/.test(near)) continue;
|
|
2391
|
+
if (!TWOFA_PASSWORD.test(near)) continue;
|
|
2392
|
+
return [{ line: j + 1, detail: "a password field inside a first-time 2FA enrollment" }];
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
return [];
|
|
2396
|
+
}
|
|
2397
|
+
var LISTBOX_ROLE = /role=["'{\s]*["']?(?:listbox|option)\b|aria-activedescendant/;
|
|
2398
|
+
var LISTBOX_OPEN = /\bset(?:Open|IsOpen|DropdownOpen|MenuOpen|Expanded)\b|\bisOpen\b|\baria-expanded\b/;
|
|
2399
|
+
var LISTBOX_OPTIONS = /\b(?:options|choices)\b/;
|
|
2400
|
+
var LISTBOX_MITIGATED = /@enigmax\/primitives|createSelect|useSelectContext|<Select|SelectRoot|radix-ui|@headlessui|downshift|react-select|@mui\/material|antd|@mantine|@chakra-ui|primereact|enigma:allow-hand-rolled-select/;
|
|
2401
|
+
function handRolledSelect(content) {
|
|
2402
|
+
if (LISTBOX_MITIGATED.test(content)) return [];
|
|
2403
|
+
if (!LISTBOX_ROLE.test(content) || !LISTBOX_OPEN.test(content) || !LISTBOX_OPTIONS.test(content)) return [];
|
|
2404
|
+
const lines = content.split("\n");
|
|
2405
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2406
|
+
const line = lines[i];
|
|
2407
|
+
if (COMMENT_LINE.test(line) || /enigma:/.test(line)) continue;
|
|
2408
|
+
if (!LISTBOX_ROLE.test(line)) continue;
|
|
2409
|
+
return [{ line: i + 1, detail: "a listbox role over an options list with its own open state" }];
|
|
2410
|
+
}
|
|
2411
|
+
return [];
|
|
2412
|
+
}
|
|
2413
|
+
var TOAST_QUEUE = /\bset(?:Toasts?|Snackbars?)\b|\btoasts\.(?:map|filter|slice)\b|\btoastQueue\b/;
|
|
2414
|
+
var TOAST_TIMER = /setTimeout\(/;
|
|
2415
|
+
var TOAST_MITIGATED = /@enigmax\/primitives|createNotifications|useNotifications|<Toaster|ToastT\b|ToasterProps\b|["']sonner["']|react-hot-toast|react-toastify|notistack|@radix-ui|@mui\/material|@chakra-ui|@mantine|antd|enigma:allow-hand-rolled-toast/;
|
|
2416
|
+
function handRolledToast(content) {
|
|
2417
|
+
if (TOAST_MITIGATED.test(content)) return [];
|
|
2418
|
+
if (!TOAST_QUEUE.test(content) || !TOAST_TIMER.test(content)) return [];
|
|
2419
|
+
const lines = content.split("\n");
|
|
2420
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2421
|
+
const line = lines[i];
|
|
2422
|
+
if (COMMENT_LINE.test(line) || /enigma:/.test(line)) continue;
|
|
2423
|
+
if (!TOAST_QUEUE.test(line)) continue;
|
|
2424
|
+
return [{ line: i + 1, detail: "a queue of toasts dismissed by a timer" }];
|
|
2425
|
+
}
|
|
2426
|
+
return [];
|
|
2427
|
+
}
|
|
2428
|
+
var PALETTE_SHORTCUT = /key\s*===?\s*["'`][kK]["'`]/;
|
|
2429
|
+
var PALETTE_MODIFIER = /metaKey|ctrlKey/;
|
|
2430
|
+
var PALETTE_OPEN = /\bset(?:Open|IsOpen|PaletteOpen|CommandOpen|Palette)\b/;
|
|
2431
|
+
var PALETTE_FILTER = /\bquery\b|\bfilter\(|\bsearch\b/i;
|
|
2432
|
+
var PALETTE_MITIGATED = /@enigmax\/primitives|isPaletteShortcut|usePaletteContext|<SearchPalette|PaletteRoot|["']cmdk["']|\bkbar\b|enigma:allow-hand-rolled-palette/;
|
|
2433
|
+
function handRolledPalette(content) {
|
|
2434
|
+
if (PALETTE_MITIGATED.test(content)) return [];
|
|
2435
|
+
if (!PALETTE_SHORTCUT.test(content) || !PALETTE_MODIFIER.test(content)) return [];
|
|
2436
|
+
if (!PALETTE_OPEN.test(content) || !PALETTE_FILTER.test(content)) return [];
|
|
2437
|
+
const lines = content.split("\n");
|
|
2438
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2439
|
+
const line = lines[i];
|
|
2440
|
+
if (COMMENT_LINE.test(line) || /enigma:/.test(line)) continue;
|
|
2441
|
+
if (!PALETTE_SHORTCUT.test(line)) continue;
|
|
2442
|
+
return [{ line: i + 1, detail: "Ctrl/Cmd+K opening a filtered panel" }];
|
|
2443
|
+
}
|
|
2444
|
+
return [];
|
|
2445
|
+
}
|
|
2171
2446
|
var CLIP_ONE_LINE = /\btruncate\b|\btext-ellipsis\b|text-overflow\s*:\s*ellipsis/;
|
|
2172
2447
|
var DYNAMIC_CHILD = /(?<![=!<>-])>\s*\{(?![#/:@])/;
|
|
2173
2448
|
var VALUE_REACHABLE = /\btitle\s*=|aria-label\s*=|<\s*Tooltip|TooltipTrigger|data-tooltip|hoverCard|HoverCard/i;
|
|
@@ -2747,7 +3022,12 @@ export {
|
|
|
2747
3022
|
formatFindings,
|
|
2748
3023
|
handRolledColorPicker,
|
|
2749
3024
|
handRolledContextMenu,
|
|
3025
|
+
handRolledLightbox,
|
|
3026
|
+
handRolledPalette,
|
|
3027
|
+
handRolledSelect,
|
|
2750
3028
|
handRolledSelection,
|
|
3029
|
+
handRolledToast,
|
|
3030
|
+
handRolledVideoPlayer,
|
|
2751
3031
|
loadRules,
|
|
2752
3032
|
missingPathAlias,
|
|
2753
3033
|
missingWindowsHide,
|
|
@@ -2765,6 +3045,7 @@ export {
|
|
|
2765
3045
|
summarizeLedger,
|
|
2766
3046
|
textareaSizeBounds,
|
|
2767
3047
|
truncatedValueUnreachable,
|
|
3048
|
+
twoFactorPasswordPrompt,
|
|
2768
3049
|
unboundedRemoteList,
|
|
2769
3050
|
viewBlankedWhileLoading,
|
|
2770
3051
|
wideNamedImports
|