enigma-cli 1.35.8 → 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.
- package/README.md +12 -11
- package/assets/registry/primitives/registry.json +688 -0
- package/assets/registry/utils/registry.json +109 -0
- 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 +7 -0
- 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.json +1 -1
- 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/dist/guardrails.js +195 -2
- package/package.json +1 -1
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
{
|
|
2
|
+
"registry": "@enigmax/utils",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"kind": "utility",
|
|
5
|
+
"items": [
|
|
6
|
+
{
|
|
7
|
+
"name": "cache",
|
|
8
|
+
"title": "Short-TTL read cache",
|
|
9
|
+
"description": "Read-through cache with in-flight deduplication, prefix invalidation and optional local/session persistence.",
|
|
10
|
+
"targets": [
|
|
11
|
+
"vanilla",
|
|
12
|
+
"astro",
|
|
13
|
+
"react"
|
|
14
|
+
],
|
|
15
|
+
"entry": {
|
|
16
|
+
"vanilla": "@enigmax/utils",
|
|
17
|
+
"astro": "@enigmax/utils",
|
|
18
|
+
"react": "@enigmax/utils/react"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
"vanilla": [
|
|
22
|
+
"createCache"
|
|
23
|
+
],
|
|
24
|
+
"astro": [
|
|
25
|
+
"createCache"
|
|
26
|
+
],
|
|
27
|
+
"react": [
|
|
28
|
+
"useCached"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
{
|
|
33
|
+
"path": "src/core/cache.ts",
|
|
34
|
+
"dest": "cache/cache.ts",
|
|
35
|
+
"targets": [
|
|
36
|
+
"vanilla",
|
|
37
|
+
"astro",
|
|
38
|
+
"react"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"path": "src/react/use-cached.ts",
|
|
43
|
+
"dest": "cache/use-cached.ts",
|
|
44
|
+
"targets": [
|
|
45
|
+
"react"
|
|
46
|
+
],
|
|
47
|
+
"rewrite": {
|
|
48
|
+
"@/core/cache": "./cache"
|
|
49
|
+
},
|
|
50
|
+
"main": true
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"styles": false,
|
|
54
|
+
"docs": "docs/notes/primitives.md#cache"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
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
|
+
],
|
|
65
|
+
"entry": {
|
|
66
|
+
"vanilla": "@enigmax/utils",
|
|
67
|
+
"astro": "@enigmax/utils",
|
|
68
|
+
"react": "@enigmax/utils"
|
|
69
|
+
},
|
|
70
|
+
"exports": {
|
|
71
|
+
"vanilla": [
|
|
72
|
+
"checkPasswordBreach"
|
|
73
|
+
],
|
|
74
|
+
"astro": [
|
|
75
|
+
"checkPasswordBreach"
|
|
76
|
+
],
|
|
77
|
+
"react": [
|
|
78
|
+
"checkPasswordBreach"
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
"files": [
|
|
82
|
+
{
|
|
83
|
+
"path": "src/core/cache.ts",
|
|
84
|
+
"dest": "password-breach/cache.ts",
|
|
85
|
+
"targets": [
|
|
86
|
+
"vanilla",
|
|
87
|
+
"astro",
|
|
88
|
+
"react"
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
{
|
|
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
|
+
}
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
"styles": false,
|
|
106
|
+
"docs": "docs/notes/primitives.md#password-breach"
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
}
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
7
|
+
"cliVersion": "1.35.13",
|
|
8
8
|
"sha": "9e30ee7d8a1a1e8c6e7f4e043857cd01841c68a427752e45bc0cad9ec5cfa279"
|
|
9
9
|
}
|
|
@@ -21,6 +21,13 @@ description: Frontend architecture - reusable components, abstraction thresholds
|
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
+
## Headless Primitives Before Hand-Rolled Interaction
|
|
25
|
+
|
|
26
|
+
- Interaction logic - looping/draggable rows, momentum, focus traps, virtualized lists, dismiss timers, read caches - is BEHAVIOR, and behavior that has already been measured and tested is a dependency, not a snippet to rewrite. Check the catalogue before writing it: `enigma add` lists it, `enigma add <name>` adds it as a dependency, `enigma add <name> --copy` vendors the source in when it must be edited.
|
|
27
|
+
- These primitives ship NO visual styles. They apply only what the behavior requires (overflow, touch-action, user-select, will-change, transform) and publish their state as `data-*` attributes, so the look stays entirely yours.
|
|
28
|
+
- Do not hand-roll one of them "just for this page". The hand-rolled version is where the measured bugs come back: a duration-driven marquee that accelerates as items are added, a `setPointerCapture` drag that silently stops every link in the row from opening, a hover slowdown that sticks forever after the first tap because touch never fires `pointerleave`.
|
|
29
|
+
- If the catalogue has no primitive for what you need, write it as one (core + adapter + registry entry + test) rather than inline in a page, so the next screen reuses it instead of rewriting it.
|
|
30
|
+
|
|
24
31
|
## Component Reuse (Mandatory)
|
|
25
32
|
|
|
26
33
|
- ALWAYS reuse a single base component and drive its behavior with props; NEVER create separate components for variants of the same element.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"version": "1.30.1",
|
|
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
|
-
"updated": "2026-08-
|
|
7
|
-
"cliVersion": "1.35.
|
|
8
|
-
"sha": "
|
|
6
|
+
"updated": "2026-08-13T18:04:26+02:00",
|
|
7
|
+
"cliVersion": "1.35.13",
|
|
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.
|
|
7
|
+
"cliVersion": "1.35.13",
|
|
8
8
|
"sha": "f55f4ed5474d5c6e7fd0804d88ac38fb3c9c3cd863519645d1415c02f4416db7"
|
|
9
9
|
}
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
7
|
+
"cliVersion": "1.35.13",
|
|
8
8
|
"sha": "24928a4c9eed28ebede9f23f746b09a9d3d8176f8fcca61a7780a853fa5bed4e"
|
|
9
9
|
}
|
package/bin/checksums.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"enigma-darwin-arm64": "
|
|
3
|
-
"enigma-linux-arm64": "
|
|
4
|
-
"enigma-linux-x64": "
|
|
5
|
-
"enigma-win32-x64.exe": "
|
|
2
|
+
"enigma-darwin-arm64": "f117d39bb0bc4c83397200387e93b98f754afe392dccc3952b6eda3f7b8100d7",
|
|
3
|
+
"enigma-linux-arm64": "612d69fbf69e4c5b954c74e74e4cf848fa29fe6f59b040ed46629df1c629e5a0",
|
|
4
|
+
"enigma-linux-x64": "25b7cd9f1c28b4ed09946d1496446b17cc1a41c42154f4b3fd752c5af971e090",
|
|
5
|
+
"enigma-win32-x64.exe": "bc95cb8d5ec3dff23af0d845646e0b37b10bda2d3e9b29726440f78dccaa041f"
|
|
6
6
|
}
|
package/dist/guardrails.js
CHANGED
|
@@ -141,6 +141,72 @@ var BUILTIN_RULES = [
|
|
|
141
141
|
// idiomatic and has no detectable signature, and Rust's serde typed deserialization already
|
|
142
142
|
// enforces shape - a rule for either would false-positive. The generic "validate every input"
|
|
143
143
|
// principle for those languages lives in the always-on memory kernel instead.
|
|
144
|
+
{
|
|
145
|
+
id: "fe-marquee-duration",
|
|
146
|
+
label: "Looping row driven by a speed, not a duration",
|
|
147
|
+
files: ["*.ts", "*.tsx", "*.js", "*.jsx", "*.css", "*.scss", "*.astro", "*.vue", "*.svelte"],
|
|
148
|
+
excludeFiles: [
|
|
149
|
+
"*.test.*",
|
|
150
|
+
"*.spec.*",
|
|
151
|
+
"*.stories.*",
|
|
152
|
+
"*.min.js",
|
|
153
|
+
"*.min.css",
|
|
154
|
+
"**/tests/**",
|
|
155
|
+
"tests/**",
|
|
156
|
+
"**/__tests__/**",
|
|
157
|
+
"__tests__/**",
|
|
158
|
+
"**/dist/**",
|
|
159
|
+
"dist/**",
|
|
160
|
+
"**/build/**",
|
|
161
|
+
"build/**",
|
|
162
|
+
"**/node_modules/**",
|
|
163
|
+
"node_modules/**",
|
|
164
|
+
"**/vendor/**",
|
|
165
|
+
"vendor/**"
|
|
166
|
+
],
|
|
167
|
+
ignoreFileCase: true,
|
|
168
|
+
scope: "file",
|
|
169
|
+
// Only newly written code: a duration-driven marquee is common in existing
|
|
170
|
+
// pages, and the rule has to name the one being added, not the backlog.
|
|
171
|
+
stage: "diff",
|
|
172
|
+
// A duration named on the same line as the looping row it drives. Requiring
|
|
173
|
+
// both words keeps every unrelated animation-duration out of the match.
|
|
174
|
+
pattern: "(marquee|ticker|logo-?wall|infinite-?scroll)[^\\n]*(animation-duration|animationDuration|--duration)|(animation-duration|animationDuration|--duration)[^\\n]*(marquee|ticker|logo-?wall|infinite-?scroll)",
|
|
175
|
+
absent: "px/s|pixels per second|pixelsPerSecond|\\bspeed\\b|@enigmax/primitives|useMarquee|createMarquee|enigma:allow-marquee-duration",
|
|
176
|
+
message: "Looping row driven by a duration. A lap is as long as its content, so the speed becomes content/duration and the row runs faster every time an item is added - measured at 45, 67 and 87 px/s for one rail at 10, 15 and 20 items. Take a speed in px/s and derive the duration, or use the primitive that already does: `enigma add marquee` (@enigmax/primitives), which also measures the lap from the DOM instead of computing it. Mark the line `enigma:allow-marquee-duration` when the duration is genuinely the contract.",
|
|
177
|
+
severity: "warn",
|
|
178
|
+
skill: "frontend-policy"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: "fe-pointer-capture-drag",
|
|
182
|
+
label: "Drag bound to window, not setPointerCapture",
|
|
183
|
+
files: ["*.ts", "*.tsx", "*.js", "*.jsx", "*.astro", "*.vue", "*.svelte"],
|
|
184
|
+
excludeFiles: [
|
|
185
|
+
"*.test.*",
|
|
186
|
+
"*.spec.*",
|
|
187
|
+
"*.stories.*",
|
|
188
|
+
"*.min.js",
|
|
189
|
+
"**/tests/**",
|
|
190
|
+
"tests/**",
|
|
191
|
+
"**/__tests__/**",
|
|
192
|
+
"__tests__/**",
|
|
193
|
+
"**/dist/**",
|
|
194
|
+
"dist/**",
|
|
195
|
+
"**/build/**",
|
|
196
|
+
"build/**",
|
|
197
|
+
"**/node_modules/**",
|
|
198
|
+
"node_modules/**",
|
|
199
|
+
"**/vendor/**",
|
|
200
|
+
"vendor/**"
|
|
201
|
+
],
|
|
202
|
+
scope: "file",
|
|
203
|
+
stage: "diff",
|
|
204
|
+
pattern: "\\.setPointerCapture\\s*\\(",
|
|
205
|
+
absent: "enigma:allow-pointer-capture",
|
|
206
|
+
message: "setPointerCapture retargets the compatibility mouse events too, so a plain click on a descendant arrives on the capturing element and the descendant's link never opens - a row of nine logo links quietly stops being nine links and nothing in the source looks wrong. Bind pointermove/pointerup/pointercancel to `window` instead, which is all the capture was for: a mousedown already captures the mouse at the OS level, so a release outside the window still arrives. Fine when the element has no interactive descendants (a slider thumb, a resize handle) - mark it `enigma:allow-pointer-capture` there (frontend-policy).",
|
|
207
|
+
severity: "warn",
|
|
208
|
+
skill: "frontend-policy"
|
|
209
|
+
},
|
|
144
210
|
{
|
|
145
211
|
id: "fe-password-input",
|
|
146
212
|
label: "Reusable password input (show/hide)",
|
|
@@ -263,6 +329,133 @@ var BUILTIN_RULES = [
|
|
|
263
329
|
severity: "block",
|
|
264
330
|
skill: "validation-policy"
|
|
265
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
|
+
},
|
|
266
459
|
{
|
|
267
460
|
id: "sec-password-breach-check",
|
|
268
461
|
label: "A new password is checked against the breach corpus",
|
|
@@ -294,7 +487,7 @@ var BUILTIN_RULES = [
|
|
|
294
487
|
// it, including a call into a shared hook whose name carries `pwned`/`breach`.
|
|
295
488
|
pattern: `autocomplete=\\{?["']new-password["']`,
|
|
296
489
|
absent: "pwnedpasswords|haveibeenpwned|hibp|pwned|breach|enigma:allow-no-breach-check",
|
|
297
|
-
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).",
|
|
298
491
|
severity: "block",
|
|
299
492
|
skill: "security-policy"
|
|
300
493
|
},
|
|
@@ -334,7 +527,7 @@ var BUILTIN_RULES = [
|
|
|
334
527
|
// of an equality/containment operator.
|
|
335
528
|
pattern: `autocomplete=\\{?["']new-password["']`,
|
|
336
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",
|
|
337
|
-
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).",
|
|
338
531
|
severity: "block",
|
|
339
532
|
skill: "security-policy"
|
|
340
533
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "enigma-cli",
|
|
3
|
-
"version": "1.35.
|
|
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": {
|