enigma-cli 1.33.1 → 1.33.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/assets/skills/anti-overengineering-policy/skill.json +1 -1
  2. package/assets/skills/anti-overengineering-review/skill.json +1 -1
  3. package/assets/skills/backend-policy/SKILL.md +1 -1
  4. package/assets/skills/backend-policy/skill.json +4 -4
  5. package/assets/skills/ciphera-style-policy/skill.json +1 -1
  6. package/assets/skills/code-review-policy/skill.json +1 -1
  7. package/assets/skills/core-engineering-policy/skill.json +1 -1
  8. package/assets/skills/database-expert/SKILL.md +12 -2
  9. package/assets/skills/database-expert/skill.json +5 -5
  10. package/assets/skills/debugging-policy/skill.json +1 -1
  11. package/assets/skills/dependency-policy/skill.json +1 -1
  12. package/assets/skills/email-policy/skill.json +1 -1
  13. package/assets/skills/frontend-design/skill.json +1 -1
  14. package/assets/skills/frontend-policy/SKILL.md +48 -1
  15. package/assets/skills/frontend-policy/skill.json +5 -5
  16. package/assets/skills/git-policy/skill.json +1 -1
  17. package/assets/skills/logo-sourcing-policy/skill.json +1 -1
  18. package/assets/skills/security-policy/SKILL.md +34 -1
  19. package/assets/skills/security-policy/skill.json +5 -5
  20. package/assets/skills/skill-creator/skill.json +1 -1
  21. package/assets/skills/task-completion-policy/skill.json +1 -1
  22. package/assets/skills/technical-writing-policy/skill.json +1 -1
  23. package/assets/skills/testing-policy/skill.json +1 -1
  24. package/assets/skills/validation-policy/SKILL.md +10 -1
  25. package/assets/skills/validation-policy/skill.json +3 -3
  26. package/bin/checksums.json +4 -4
  27. package/dist/guardrails.js +203 -2
  28. package/package.json +1 -1
@@ -7,6 +7,6 @@
7
7
  "minimalCode"
8
8
  ],
9
9
  "updated": "2026-07-17T00:17:02+02:00",
10
- "cliVersion": "1.33.1",
10
+ "cliVersion": "1.33.3",
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.33.1",
7
+ "cliVersion": "1.33.3",
8
8
  "sha": "f742a2be3f328b9ea1ff9a35a449177c2cbec35ad16e46f7054b7a873a2ab017"
9
9
  }
@@ -17,7 +17,7 @@ description: Backend/API architecture - controller-service-repository layering,
17
17
  - Separate controllers, services, repositories, and validators into distinct layers.
18
18
  - Controller/route handler: parse and validate input, call a service, shape the response. No business logic.
19
19
  - Service: business logic and orchestration. Reusable and domain-focused. No HTTP or framework details.
20
- - Repository/data access: the only layer that talks to the database (per database-expert). No business logic.
20
+ - Repository/data access: the only layer that talks to the database (per database-expert). No business logic. On a TypeScript/JavaScript/Node/Bun stack that layer is the Prisma client over PostgreSQL by default - pick it without asking, and use another ORM only when the user, the requirements, or the existing codebase names one.
21
21
  - Validator/schema: input contracts via Zod or equivalent (per validation-policy).
22
22
  - Do not place business logic in route handlers, and do not place data access in services - go through the repository.
23
23
 
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "backend-policy",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Backend/API architecture: controller-service-repository layering, API and request optimization (batching, avoiding redundant calls, skipping no-op writes), server-side caching (Redis), and Zod boundary validation.",
6
- "updated": "2026-07-31T16:34:48+02:00",
7
- "cliVersion": "1.33.1",
8
- "sha": "768c2e088948ff97d6b897ce43854e53dfd156738e6a19548679b5327278433d"
6
+ "updated": "2026-08-01T15:51:38+02:00",
7
+ "cliVersion": "1.33.3",
8
+ "sha": "572fcf9780e3892942c300214367d7205ed6dcde173a64c61c1e519506cf0cfa"
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, comments, code-level anti-patterns; TypeScript-first, language-agnostic).",
6
6
  "updated": "2026-07-31T21:32:28+02:00",
7
- "cliVersion": "1.33.1",
7
+ "cliVersion": "1.33.3",
8
8
  "sha": "d6a86c28ad39ee502607a5ca8f6f361c8aa52b8fa946156165ec78c6139e495f"
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-06-01T00:45:28+02:00",
7
- "cliVersion": "1.33.1",
7
+ "cliVersion": "1.33.3",
8
8
  "sha": "3d3bbe0602d5bbb4afe37648fe3c2fa39376b1bcbac5d8c441f01fad1e866ed0"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Core engineering execution policy and harness orchestration (highest-authority rules).",
6
6
  "updated": "2026-07-30T19:29:19+02:00",
7
- "cliVersion": "1.33.1",
7
+ "cliVersion": "1.33.3",
8
8
  "sha": "6a881d8589926fa7f48058314fd26d7042fd2ac82f1c87a6c11ffb54d1fda22b"
9
9
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: database-expert
3
- description: Senior database architecture - engine selection (PostgreSQL is the default relational engine for anything deployed or multi-writer; SQLite only for local-first, embedded, single-writer stores), schema design, normalization and anti-duplication, query/index optimization, scalability (partitioning, sharding, replication), and RGPD/GDPR encryption of sensitive data. Use when designing, modifying, migrating, querying, or reviewing any database, schema, SQL, ORM model, or persistence layer, and when choosing the datastore for a new project's stack.
3
+ description: Senior database architecture - engine selection (PostgreSQL is the default relational engine for anything deployed or multi-writer; SQLite only for local-first, embedded, single-writer stores), ORM selection (TypeScript/JavaScript/Node/Bun projects use Prisma over PostgreSQL unless the user or the requirements name another ORM), schema design, normalization and anti-duplication, query/index optimization, scalability (partitioning, sharding, replication), and RGPD/GDPR encryption of sensitive data. Use when designing, modifying, migrating, querying, or reviewing any database, schema, SQL, ORM model, or persistence layer, and when choosing the datastore for a new project's stack.
4
4
  ---
5
5
 
6
6
  # Database Expert Policy (Senior Data Architecture Standards)
@@ -34,7 +34,17 @@ description: Senior database architecture - engine selection (PostgreSQL is the
34
34
  - MySQL/MariaDB only when the platform, the host or the team requires it. SQL Server or Oracle only where it is already the environment.
35
35
  - Do not add a second datastore before PostgreSQL runs out. It handles queues (`SELECT ... FOR UPDATE SKIP LOCKED`), full-text search, vectors, JSON documents and counters well past early scale. Add Redis, a search engine or a vector database when a measured limit demands it, not as part of the initial stack.
36
36
  - Serverless and edge runtimes still get PostgreSQL: the problem there is connection count, not the engine, so put a pooler in front (PgBouncer, Prisma Accelerate, the provider's pooled endpoint) instead of switching to a file database.
37
- - Wire it the same way every time: Prisma as the ORM for TypeScript, migrations committed to version control, the connection string from the environment and never in the repo, and pooling configured before the first load test rather than after the first outage.
37
+ - Wire it the same way every time: the ORM below, migrations committed to version control, the connection string from the environment and never in the repo, and pooling configured before the first load test rather than after the first outage.
38
+
39
+ ---
40
+
41
+ ## ORM Selection (TypeScript/JavaScript: Prisma)
42
+
43
+ - Every TypeScript, JavaScript, Node, Bun or Deno project uses **Prisma over PostgreSQL**. Pick it without being asked - it is the default stack, not one option among several to weigh up.
44
+ - Use something else only when the user asks for another ORM, or the requirements or the existing codebase already commit to one. Then follow that choice and say in one line what it is.
45
+ - Treat Prisma as the schema's source of truth: `schema.prisma` defines the models, `prisma migrate` generates the migrations and they are committed, and the generated client is the query path. Do not hand-write parallel DDL for the same tables.
46
+ - Raw SQL stays available for what the query builder does poorly - `$queryRaw` with parameters, never interpolated input.
47
+ - The identifier policy below still applies: UUID primary keys - `@id @default(uuid(7))` where the installed Prisma version accepts the version argument, otherwise an application-generated UUIDv7/ULID - and never `autoincrement()`.
38
48
 
39
49
  ---
40
50
 
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "database-expert",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "provider": "FJRG2007/enigma",
5
- "description": "Senior database architecture policy: engine selection (PostgreSQL by default, SQLite only for local-first/embedded stores), query optimization, anti-duplication/normalization, scalability, and RGPD/GDPR encryption.",
6
- "updated": "2026-07-31T19:05:08+02:00",
7
- "cliVersion": "1.33.1",
8
- "sha": "b21ae85a015305106f787be2eb0a022661b2e984bc42c6bc66703954256e3c53"
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
+ "updated": "2026-08-01T15:51:38+02:00",
7
+ "cliVersion": "1.33.3",
8
+ "sha": "4b7569446786c5bdbb71b2e9102aee0d8dd391b5e3e461d6fb1f05294d446ad9"
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-06-01T00:45:28+02:00",
7
- "cliVersion": "1.33.1",
7
+ "cliVersion": "1.33.3",
8
8
  "sha": "14b0064c8b33a0dc85e51464b05005cf5801c756b1101789a6924b9548420f6b"
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.33.1",
7
+ "cliVersion": "1.33.3",
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.33.1",
7
+ "cliVersion": "1.33.3",
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.33.1",
7
+ "cliVersion": "1.33.3",
8
8
  "sha": "9e30ee7d8a1a1e8c6e7f4e043857cd01841c68a427752e45bc0cad9ec5cfa279"
9
9
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: frontend-policy
3
- description: Frontend architecture - reusable components, abstraction thresholds, state management, no-op detection (skip any operation whose result equals the current state - form saves, toggles, filters, reorders - not just saves; dirty means the values DIFFER from the loaded snapshot, not that the user touched the field, so a value edited and put back leaves Save disabled), client-side caching (localStorage/sessionStorage to avoid redundant server calls and survive rate limits), instant first paint (render the shell immediately, load data async via the API, show skeletons - never block render on data), perceived performance and responsiveness (instant interaction feedback, prefetch on intent, debounce/throttle, cancel stale requests, avoid request waterfalls, lazy-load heavy widgets), large-list rendering (virtualized infinite scroll vs pagination, skeletons, progressive/parallel loading, short-TTL caching), optimistic UI with rollback, visual restraint (never a card inside a card, borders only where they carry information, spacing and background tone before chrome), icon actions (repeated row/card actions like copy, edit, rename, remove, download, refresh are icon-only buttons carrying aria-label plus title, never a text label), responsive/adaptive layout (fluid units, breakpoints, no overlap or horizontal overflow, viewport meta, touch targets), form fields that declare their keyboard and casing (autocapitalize/autocomplete/inputmode/spellcheck per field kind, set once in the shared Input, normalized on blur rather than on every keystroke, with an inline error on every field that has a rule), AI chat/assistant/agent interfaces (use Vercel's AI Elements registry for message threads, streaming, reasoning and tool-call panels, prompt inputs - never hand-roll chat UI in React), and periodic React code-health audits (react-doctor). Use when building or changing UI components, client state, forms/save flows, data fetching/caching, lists that show lots of data, loading states, dashboards/panels, layout/responsiveness, making the UI feel fast, building a chat/AI/agent/LLM interface, or any frontend structure.
3
+ description: Frontend architecture - reusable components, abstraction thresholds, state management, no-op detection (skip any operation whose result equals the current state - form saves, toggles, filters, reorders - not just saves; dirty means the values DIFFER from the loaded snapshot, not that the user touched the field, so a value edited and put back leaves Save disabled), client-side caching (localStorage/sessionStorage to avoid redundant server calls and survive rate limits), instant first paint (render the shell immediately, load data async via the API, show skeletons - never block render on data), perceived performance and responsiveness (instant interaction feedback, prefetch on intent, debounce/throttle, cancel stale requests, avoid request waterfalls, lazy-load heavy widgets), large-list rendering (virtualized infinite scroll vs pagination, skeletons, progressive/parallel loading, short-TTL caching), optimistic UI with rollback, visual restraint (never a card inside a card, borders only where they carry information, spacing and background tone before chrome), icon actions (repeated row/card actions like copy, edit, rename, remove, download, refresh are icon-only buttons carrying aria-label plus title, never a text label), navigation that is iconified and grouped into labelled sections once it outgrows a flat list, a Cmd/Ctrl+K command palette with fuse.js fuzzy search over the loaded data once the app has enough destinations and records to hunt through, responsive/adaptive layout (fluid units, breakpoints, no overlap or horizontal overflow, viewport meta, touch targets), form fields that declare their keyboard and casing (autocapitalize/autocomplete/inputmode/spellcheck per field kind, set once in the shared Input, normalized on blur rather than on every keystroke, with an inline error on every field that has a rule), auth screens (breached-password feedback, strength meter, cookie consent answered before login/register), AI chat/assistant/agent interfaces (use Vercel's AI Elements registry for message threads, streaming, reasoning and tool-call panels, prompt inputs - never hand-roll chat UI in React), and periodic React code-health audits (react-doctor). Use when building or changing UI components, client state, forms/save flows, data fetching/caching, lists that show lots of data, loading states, dashboards/panels, layout/responsiveness, making the UI feel fast, building a chat/AI/agent/LLM interface, or any frontend structure.
4
4
  ---
5
5
 
6
6
  # Frontend Architecture Policy
@@ -146,6 +146,24 @@ A sidebar keeps its desktop width only while there is a desktop to put it in. On
146
146
 
147
147
  ---
148
148
 
149
+ ## Navigation Is Structured, Not A Growing List
150
+
151
+ A sidebar gets one more entry per feature and nobody ever goes back to reorganize it, so it ends as a flat column of a dozen similar words that has to be read top to bottom every time. Structure it as it grows, by default and without being asked: the icons, the grouping and the ordering below are part of building the nav, not a redesign to propose afterwards.
152
+
153
+ **Every entry carries an icon.** One icon set for the whole nav, one size, aligned in a single column so the labels line up. The glyph is what the eye aims at once the user knows the app, and it is what makes a collapsed rail possible later. Choose it for what the destination IS, not for the word in the label: a gauge for a dashboard, a key for credentials, sliders for settings. When no glyph reads for an abstract destination, take the set's neutral placeholder rather than a loose near-match - a wrong icon is read as a different feature.
154
+
155
+ **The label stays.** This is the opposite call from Icon Actions above, and the difference is repetition: a row action repeats per row and its glyph is conventional, while a nav entry appears once and names a place the user may never have visited. Icon plus word, and the icon takes `aria-hidden="true"` because the label is already the accessible name.
156
+
157
+ **Group once the list passes about seven entries.** Labelled sections of related destinations, with the label a quiet uppercase or muted heading rather than another bordered box (Visual Hierarchy above). Order the groups and the entries inside them by how often they are used, never alphabetically - alphabetical order is the one arrangement that guarantees the daily destination is somewhere in the middle. Account, billing and sign out sit apart at the bottom, separated by space.
158
+
159
+ **Mark the active entry with more than colour**: a filled background or a leading bar plus `aria-current="page"`, so it survives a colourblind user and a screen reader. A group holding the active entry stays expanded.
160
+
161
+ **Collapsible groups persist their state.** If a section can be collapsed, remember the choice per user (localStorage is enough) and restore it on the next visit; a nav that reopens every group on every navigation is worse than one that never collapsed.
162
+
163
+ **Nav is not the only way in.** Past roughly a dozen destinations, grouping stops being enough and the user starts hunting: add the command palette from Search & Filtering below. Structure narrows the hunt; search ends it.
164
+
165
+ ---
166
+
149
167
  ## Links In Copy Are Links
150
168
 
151
169
  When UI copy names a destination - a URL, a doc page, a dashboard, a settings screen, an external service - make it reachable from where it is written. Printing a bare URL as plain text in a hint, description, empty state or error message leaves the user to select and copy it by hand, which is exactly the work the interface exists to remove.
@@ -261,6 +279,10 @@ Auth is the first screen a user meets and the one most often shipped half-built.
261
279
  - Every sign-in form with a password field carries a visible "Forgot your password?" link next to that field, leading to a real reset flow. Building the login screen without it is shipping a dead end.
262
280
  - The reset request screen confirms in the same words whether or not the address is registered ("If that address has an account, we have sent a link"). Never render "no account with that email" - the screen would be an account-existence oracle.
263
281
  - The new-password screen validates in real time against the same schema the server uses, uses the shared Input (which brings the show/hide toggle), and compares the confirmation field as the user types. Keep Submit disabled until both are valid, with the reason visible.
282
+ - Wherever a password is created (sign-up, reset, change), check it against Have I Been Pwned's Pwned Passwords range API as the user types - debounced, request aborted when the value changes - and refuse a breached one with "This password appeared in a data breach. Choose a different one." Free, no key, and the password never leaves the browser: only the first 5 characters of its SHA-1 do (mechanics and the server-side half in security-policy).
283
+ - The same screens refuse a password built out of the identity it protects: the email, its local part, the username, the display name, the site name. Compare normalized values (lowercased, accent-stripped, punctuation dropped) so `F.J.R.G_2007` and `fjrg2007` are one string, and check containment and near-matches, not just equality (the rule and the server half are security-policy's). The form already holds the email and the name the user just typed, so run it on every keystroke and name the reason inline: "Your password cannot contain your email address or username."
284
+ - Next to it, a strength meter: a row of segments that fills and shifts colour (red, amber, green) with a one-line verdict. Drive it from an entropy estimator (`@zxcvbn-ts/core`), fed the user's own email and name as context, not from a regex counting character classes - "Passw0rd!" satisfies every class rule and is guessed instantly. The meter is advisory; the length floor and the breach check are the gate.
285
+ - The consent banner is answered before either form submits (security-policy owns the cookie rules). Blocking the account on a "reject" is not the ask: record the choice, then continue.
264
286
  - After sign-up the user lands inside the app, already signed in. If the account still needs email verification, say so in the app with a way to resend, and block only the actions that need it.
265
287
  - Surface throttling honestly. On a `429`, show how long the wait is (from `Retry-After`), keep the button disabled with a countdown, and never swallow the response into a generic "something went wrong".
266
288
  - A one-time-code field is one input with `autocomplete="one-time-code"`, `inputmode="numeric"`, paste of the whole code, and no clearing of what the user typed on a wrong attempt. Say how many attempts are left only if the server chose to reveal it.
@@ -268,6 +290,18 @@ Auth is the first screen a user meets and the one most often shipped half-built.
268
290
 
269
291
  ---
270
292
 
293
+ ## Cookie Consent Banner
294
+
295
+ A site that loads analytics, ads, A/B testing or session replay ships a consent surface. What may be set and when is security-policy's; this is the UI half.
296
+
297
+ - Reject is as reachable as Accept: same level, same weight, one click. Hiding refusal behind an extra screen is a dark pattern, and in the EU it is not consent at all.
298
+ - Three actions: accept all, reject all, and per-category choices with the strictly necessary group shown as always-on and not togglable. Nothing in the other categories loads until one of them is chosen.
299
+ - It is a dialog over the page, not a layout shift: focus moves into it, Escape and the keyboard work, it does not sit on top of the primary action, and closing it does not reflow the page.
300
+ - Persist the decision so it is asked once, and leave a permanent way back in (a footer link or a settings row) to change or withdraw it.
301
+ - The login and sign-up screens do not submit while the banner is unanswered. Answer it, record the choice, then continue - including when the choice was to reject.
302
+
303
+ ---
304
+
271
305
  ## Client-Side Caching (Reduce Server Load)
272
306
 
273
307
  Cache on the client to avoid redundant server round-trips and to keep the app usable under rate limits. The goal is to reach the backend (and therefore Redis/DB) as rarely as correctness allows.
@@ -414,6 +448,19 @@ For a user-facing search box or finder over a list, use fuse.js (fuzzy search) r
414
448
  - Reach for fuse.js whenever the input is a search/filter box the user types free text into. Keep a plain equality/predicate filter only for exact, structured filtering (a status dropdown, a tag toggle) where fuzziness would be wrong.
415
449
  - Configure the searched `keys` and a sensible `threshold`, and run the search over the already-loaded client list where possible (reuse the data, per Client-Side Caching) before falling back to a server query.
416
450
 
451
+ ### Ctrl+K Opens A Command Palette Once There Is Enough To Hunt For
452
+
453
+ When an app has more destinations and records than fit comfortably in the nav - roughly a dozen sidebar entries, or any list the user scrolls to find a known item - the fastest path stops being pointing and becomes typing. Ship the palette then, by default and without being asked. Before that, a three-screen app does not need one (anti-overengineering-policy).
454
+
455
+ - **The shortcut is Cmd+K on macOS and Ctrl+K elsewhere**, bound once on the document and `preventDefault`ed (Ctrl+K focuses the browser's own search bar otherwise). The same handler answers `/` where the app has no text input focused, and Escape always closes.
456
+ - **It must also be clickable.** A shortcut nobody discovers is not an entry point: put the search field in the header or at the top of the sidebar showing the shortcut hint inside it ("Search Ctrl K"), and open the palette when it is clicked. Detect the platform for the hint rather than printing both.
457
+ - **It opens centred over the page as a modal dialog**, near the top third so the results grow downwards without moving the input. It is a real dialog: `role="dialog"` with `aria-modal`, focus into the field, focus trapped while open, focus restored to the trigger on close, page scroll locked. Reuse the project's Modal - the palette is a variant of it, not a second dialog implementation (Component Reuse above).
458
+ - **Search the data the client already holds**, fuzzy-matched with fuse.js in TS/JS, re-run on every keystroke - the in-memory pass is cheap and debouncing it only adds lag. Debounce only the part that reaches the server (~200-300ms) and abort the in-flight request when the query changes.
459
+ - **Index everything the user thinks of as a thing**, not just pages: navigation destinations, records (projects, accounts, files), and actions ("Create project", "Sign out", "Toggle theme"). Group the results by kind with a heading per group, and show the destination path or a second line so two similarly named records are distinguishable.
460
+ - **Empty query is not an empty panel.** Show recent items and the handful of most likely destinations, so opening it is useful before anything is typed.
461
+ - **Full keyboard contract**: Up/Down move through results across group boundaries, Enter opens the highlighted one, Cmd/Ctrl+Enter opens it in a new tab where that makes sense, and the highlighted row is tracked with `aria-activedescendant` on a `role="listbox"`. The mouse hovering a row moves the highlight so the two never disagree.
462
+ - **A no-results state names the query** and offers the nearest useful action ("No match for 'billling' - search all records"), rather than rendering nothing.
463
+
417
464
  ---
418
465
 
419
466
  ## AI Chat & Agent Interfaces
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "frontend-policy",
3
- "version": "1.22.0",
3
+ "version": "1.23.0",
4
4
  "provider": "FJRG2007/enigma",
5
- "description": "Frontend architecture: reusable components, abstraction thresholds, state management, 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/pagination, virtualization, skeletons, progressive loading), 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), and AI chat/agent interfaces via Vercel's AI Elements registry instead of hand-rolled message threads.",
6
- "updated": "2026-07-31T19:05:08+02:00",
7
- "cliVersion": "1.33.1",
8
- "sha": "79816cdc63a7fd64336b93296aada3520bab26b38a7f65600f5ca27745de3a65"
5
+ "description": "Frontend architecture: reusable components, abstraction thresholds, state management, 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/pagination, virtualization, skeletons, progressive loading), 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-01T17:44:16+02:00",
7
+ "cliVersion": "1.33.3",
8
+ "sha": "434b0bb9dd8e19d68dc823198df64feb873e3644ae483bcd14b75d9e3d0f74ef"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Git & contribution policy (senior engineering standards).",
6
6
  "updated": "2026-07-16T22:44:02+02:00",
7
- "cliVersion": "1.33.1",
7
+ "cliVersion": "1.33.3",
8
8
  "sha": "e6dfbc33884000d9d25841bd9c5a84d6558ffd374882cb7b34451eb2cebc2161"
9
9
  }
@@ -7,6 +7,6 @@
7
7
  "logoColorPolicy"
8
8
  ],
9
9
  "updated": "2026-07-17T00:17:02+02:00",
10
- "cliVersion": "1.33.1",
10
+ "cliVersion": "1.33.3",
11
11
  "sha": "09cdbefd98625b02a7d03685e5deed128238ff8454a83fe22279610fe3ef8ddf"
12
12
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: security-policy
3
- description: Application and AI-agent security - secrets management, authentication and authorization (least privilege), credential flows (sign-in, sign-up that establishes the session, password reset, 2FA, and rate limiting per IP and per account), OWASP Top 10 mitigations, transport and crypto baseline, secure logging, and agent/MCP/tool-use safety (prompt injection, untrusted tool output, permission boundaries). Use when handling secrets, auth, login or registration screens, permissions, untrusted data or tool output, or any security-sensitive code, config, or infrastructure.
3
+ description: Application and AI-agent security - secrets management, authentication and authorization (least privilege), credential flows (sign-in, sign-up that establishes the session, password reset, 2FA, breached-password checks against Have I Been Pwned, refusing a password that repeats the username, email or display name in any casing, and rate limiting per IP and per account), cookie attributes and consent gating before non-essential storage, OWASP Top 10 mitigations, transport and crypto baseline, secure logging, and agent/MCP/tool-use safety (prompt injection, untrusted tool output, permission boundaries). Use when handling secrets, auth, login or registration screens, permissions, untrusted data or tool output, or any security-sensitive code, config, or infrastructure.
4
4
  ---
5
5
 
6
6
  # Security Policy
@@ -55,6 +55,27 @@ These four screens are one system: an attacker who cannot guess a password will
55
55
  - On a successful reset: consume the token, rotate the session, and invalidate every other active session and refresh token for that account. Notify the account by email that the password changed.
56
56
  - Never send the new password by email, and never embed credentials in the link. The link proves control of the address, nothing more.
57
57
 
58
+ ### Every new password is checked against the breach corpus
59
+
60
+ - Any screen where a password is CREATED - sign-up, reset confirmation, change password - checks the candidate against Have I Been Pwned's Pwned Passwords range API before accepting it. A password sitting in a public breach is already in every credential-stuffing list, whatever its length or symbol count says.
61
+ - The API is free, needs no key and no account. Never send the password: SHA-1 the candidate, uppercase the hex, `GET https://api.pwnedpasswords.com/range/<first 5 hex chars>`, and look for the remaining 35 characters in the response, whose lines are `SUFFIX:COUNT`. Only the 5-character prefix ever leaves the client - that k-anonymity split is what the endpoint exists for.
62
+ - Send `Add-Padding: true` so the response length cannot be used to infer which prefix was asked for.
63
+ - Run it in real time while the user types (debounce ~400ms, abort the in-flight request when the value changes) so the answer is on screen before Submit, and run it again server-side on submit. The client check is UX; the server check is the rule.
64
+ - Fail OPEN. If the lookup errors or times out, accept the password and log it: blocking every registration on a third-party outage is the worse failure.
65
+ - Say what happened and what to do - "This password appeared in a data breach. Choose a different one." The breach count is optional; blaming the user is not.
66
+ - Do not stack composition rules on top (one symbol, one digit, forced rotation). NIST SP 800-63B dropped them: a length floor (12+), the breach check, the identity check below and rate limiting are the controls that work.
67
+
68
+ ### A password may not be the account's own identity
69
+
70
+ The same screens that check the breach corpus reject a password built out of the identity it protects. `Fjrg2007` for the user `fjrg2007` is one guess for anyone holding the email address, and it is the first thing a targeted attacker tries. NIST SP 800-63B names context-specific words - the username, the service name, the address - as the other list to refuse, next to the breach corpus.
71
+
72
+ - Compare against every identifier the account is known by: the email, the email's local part, the username or handle, the display name, and the site or company name. Check each one separately; a password equal to the local part passes a check that only compared the full address.
73
+ - **Compare NORMALIZED values, never raw ones.** Lowercase both sides, trim, normalize Unicode to NFKD and strip the accents, then drop everything that is not a letter or a digit. `F.J.R.G_2007`, `FJRG2007` and `fjrg 2007` all reduce to `fjrg2007`, which is the point: case, punctuation and spacing are not differences an attacker has to guess. Reuse the shared normalizer that already canonicalizes the email and the handle (validation-policy).
74
+ - Reject on three relations, not just equality: the normalized password EQUALS an identifier, CONTAINS one that is 4 characters or longer (`myfjrg2007pass` still hands over the pattern), or is a near-match. For the near-match use a similarity ratio (Django's `UserAttributeSimilarityValidator` uses `SequenceMatcher` at 0.7); a padded year or a leetspeak swap (`fjrg2007!`, `fjrg20o7`) is the case that catches.
75
+ - **The server is the authority.** It holds the real identity, so it runs the comparison on every password-creating request even when the client already did. On sign-up the values come from the submitted form; on reset and change they come from the account being modified, never from the request body.
76
+ - A strength meter fed the user's own email and name (`@zxcvbn-ts/core` with `userInputs`) will score this password badly, and that is not the same thing: the meter is advisory and a score threshold is not a gate. Keep the equality, containment and similarity check as its own rule with its own refusal.
77
+ - Say which rule refused and what to change: "Your password cannot contain your email address or username." At creation time the user already knows their own identifiers, so this reveals nothing an attacker could use, unlike a sign-in error.
78
+
58
79
  ### Rate-limit by IP AND by account
59
80
 
60
81
  - Limit both dimensions on every credential endpoint: sign-in, sign-up, password reset request, reset confirmation, email verification, 2FA/OTP verification, and any "does this identifier exist" helper the sign-in page calls.
@@ -73,6 +94,18 @@ These four screens are one system: an attacker who cannot guess a password will
73
94
 
74
95
  ---
75
96
 
97
+ ## Cookies & Consent
98
+
99
+ - The session cookie is `HttpOnly`, `Secure`, `SameSite=Lax` (`Strict` where nothing legitimately enters the app cross-site), `Path=/`, with an explicit lifetime, and host-only unless a subdomain genuinely needs it. A token JavaScript can read is one XSS away from being someone else's session.
100
+ - `SameSite=None` demands `Secure` and a stated reason: it re-opens CSRF, so it comes with a CSRF token on every state-changing request.
101
+ - Use the `__Host-` prefix in production where the browser can enforce it (Secure, `Path=/`, no `Domain`).
102
+ - Classify every cookie before setting it: strictly necessary (session, CSRF, load balancing, the consent record itself) against everything else (analytics, A/B, ads, session replay). Only the first group may be set before the user has answered.
103
+ - Nothing in the second group runs until then. No analytics snippet, no tag manager, no third-party pixel, no `document.cookie` write, no `localStorage` copy of the same data: swapping the storage mechanism does not change the rule.
104
+ - Record the decision - what was accepted, when, against which policy version - and make withdrawing it as easy as giving it.
105
+ - An auth flow never starts with the banner unanswered: require the consent decision before sign-in or sign-up proceeds, so nothing is planted mid-flow behind the user's back. Accepting the non-essential ones must not be the price of an account - a "reject" that still signs the user in is what keeps this lawful under GDPR/ePrivacy, and the session cookie is strictly necessary anyway, so it needs no consent.
106
+
107
+ ---
108
+
76
109
  ## OWASP Top 10 Baseline
77
110
 
78
111
  - Injection (SQL/NoSQL/command/LDAP): use parameterized queries and safe APIs; never build queries or shell commands by string concatenation (query specifics in database-expert).
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "security-policy",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "provider": "FJRG2007/enigma",
5
- "description": "Application and AI-agent security: secrets, authn/authz (least privilege), OWASP Top 10, transport/crypto baseline, secure logging, and agent/MCP/tool-use safety.",
6
- "updated": "2026-07-31T16:26:45+02:00",
7
- "cliVersion": "1.33.1",
8
- "sha": "e5e7ac2791919e4b8483115ee8b83800c5ebdf4eebde019650d974b400417c55"
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
+ "updated": "2026-08-01T17:44:16+02:00",
7
+ "cliVersion": "1.33.3",
8
+ "sha": "c9eac7999b326e4564816b10f3f681b9f6b2a88595d56244909757fb4bbc3c7f"
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-07-29T01:18:36+02:00",
7
- "cliVersion": "1.33.1",
7
+ "cliVersion": "1.33.3",
8
8
  "sha": "13d24c217bdb6fe83fe16835d8f5c3d397a9f3338f16876c61ef96e97f34c90a"
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-07-30T20:10:24+02:00",
7
- "cliVersion": "1.33.1",
7
+ "cliVersion": "1.33.3",
8
8
  "sha": "2f82288d83dca3676d6bc49955c89477cc298f77fa56b4e414cd95ff858fa261"
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.33.1",
7
+ "cliVersion": "1.33.3",
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.33.1",
7
+ "cliVersion": "1.33.3",
8
8
  "sha": "3bdf591057b760f674fb2b1425f63acb426cda2c4f042e1a74c5a5d3807df664"
9
9
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: validation-policy
3
- description: Strict frontend + backend schema validation (Zod or equivalent), normalization before validation (one shared normalizer on both sides - trim, lowercase the email, capitalize each word of a name, canonicalize a profile link or handle) and the rule that a check which cannot fail is not validation, schema consistency between client and server, and safe client-facing error handling. Use when handling any external input - forms, API request bodies, query params, CLI args, file parsing, or third-party payloads.
3
+ description: Strict frontend + backend schema validation (Zod or equivalent), normalization before validation (one shared normalizer on both sides - trim, lowercase the email, capitalize each word of a name, canonicalize a profile link or handle) and the rule that a check which cannot fail is not validation, cross-field rules declared on the object schema rather than the field (password against the email or username, confirmation fields, date ranges), schema consistency between client and server, and safe client-facing error handling. Use when handling any external input - forms, API request bodies, query params, CLI args, file parsing, or third-party payloads.
4
4
  ---
5
5
 
6
6
  # Validation & Error Handling Policy
@@ -83,6 +83,15 @@ Per field kind (defaults - override only with a reason):
83
83
  - Before calling a field done, type three wrong values into it and confirm each is rejected. A validator nobody has watched fail is unverified.
84
84
  - The same applies to a permissive fallback: an `.optional()` that swallows `""`, a `catch()` that returns a default, or a `refine` that returns `true` on anything it cannot parse.
85
85
 
86
+ ### A rule about two fields lives on the object, not the field
87
+
88
+ Some rules cannot be expressed where the field is declared, because the field cannot see its siblings. `z.string().min(12)` for a password is a complete-looking schema that has no way to know the email sitting next to it, which is how "the password may not be your email" ends up unimplemented on a form that otherwise validates everything.
89
+
90
+ - Put cross-field rules on the OBJECT schema (`.superRefine`/`.refine` in Zod, a `model_validator` in Pydantic), and attach the error to the field the user has to change so it renders in that field's error slot, not at the top of the form.
91
+ - The recurring ones: password against the email, its local part, the username, the display name and the site name (normalized on both sides, per security-policy); the confirmation field against the password; a start date against an end date; a "one of these is required" pair.
92
+ - Give the object schema everything it needs to compare. On the server the identity usually comes from the account being modified rather than the request body, so build the schema with that value in scope (a factory that takes the identifiers and returns the schema) instead of trusting whatever the client sent.
93
+ - The client mirrors the same object schema so the conflict shows as the user types, and the server runs it again as the authority.
94
+
86
95
  ---
87
96
 
88
97
  ## Validation Standards
@@ -3,7 +3,7 @@
3
3
  "version": "1.4.0",
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), schema consistency, and safe client-facing error handling.",
6
- "updated": "2026-07-31T19:05:08+02:00",
7
- "cliVersion": "1.33.1",
8
- "sha": "84bb43cc570aa927ab5c0311c17242b4158287961aa04af111cf83eb4eec6ce4"
6
+ "updated": "2026-08-01T17:44:16+02:00",
7
+ "cliVersion": "1.33.3",
8
+ "sha": "225e1e26f4a49fac70714b1bebc104ea42143203cc8fcc9e3330b29f05b6e025"
9
9
  }
@@ -1,6 +1,6 @@
1
1
  {
2
- "enigma-darwin-arm64": "f9e83b3b9be12c8248957edaff5aaf673e9449524c70938a28a7c9c999946ddd",
3
- "enigma-linux-arm64": "04ebf9604c6a01bb1630d8737f297076bb2ea7c5708910dd9f6fbdfb88fc1d37",
4
- "enigma-linux-x64": "6cce08f880b4c0de0c3304129384de6a07f4055b3c2a05587a238bc764679e03",
5
- "enigma-win32-x64.exe": "e17d305ca7fff13f07df054d87e17e1333c483231d8bfde31fe5aa8898c6718e"
2
+ "enigma-darwin-arm64": "286cca0f828af2df05e34bbf8e65739191fd32828800780f639adf033da3534e",
3
+ "enigma-linux-arm64": "97df04765c3aab4ea4ed2737409cee89a4993e82627016dbdd0b6beabff06fec",
4
+ "enigma-linux-x64": "b328e77ba40317e71a0c41cac400e70f7d74fb45c33491525137ed62f359a271",
5
+ "enigma-win32-x64.exe": "3cf574dc946b3d4294ef8c368d79e18acf42de6a65100a54d41a8b98f5375d06"
6
6
  }
@@ -4,8 +4,8 @@
4
4
  import { homedir } from "os";
5
5
  import { fileURLToPath } from "url";
6
6
  import { execFileSync } from "child_process";
7
- import { readFileSync, statSync, existsSync } from "fs";
8
7
  import { dirname, join, resolve } from "path";
8
+ import { readFileSync, writeFileSync, statSync, existsSync } from "fs";
9
9
  var COMMENT_LINE = /^\s*(\/\/|#|\*|--|<!--|\{?\/\*)/;
10
10
  var BUILTIN_RULES = [
11
11
  {
@@ -195,6 +195,163 @@ var BUILTIN_RULES = [
195
195
  severity: "block",
196
196
  skill: "frontend-policy"
197
197
  },
198
+ {
199
+ id: "fe-name-value-normalize",
200
+ label: "A person-name value is normalized, not just autocapitalized",
201
+ files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.html", "*.htm"],
202
+ excludeFiles: [
203
+ "*.test.*",
204
+ "*.spec.*",
205
+ "**/tests/**",
206
+ "**/__tests__/**",
207
+ "**/stories/**",
208
+ "*.stories.*",
209
+ "*.min.js",
210
+ "**/dist/**",
211
+ "**/build/**",
212
+ "**/_build/**",
213
+ "**/node_modules/**",
214
+ "**/vendor/**",
215
+ "dist/**",
216
+ "build/**",
217
+ "_build/**",
218
+ "node_modules/**",
219
+ "vendor/**"
220
+ ],
221
+ scope: "file",
222
+ // The twin of fe-name-input-capitalize, and the half that actually reaches the stored
223
+ // value: `autocapitalize` is a KEYBOARD hint. A phone honours it, a physical keyboard
224
+ // ignores it entirely, so "juan perez" typed on a laptop is stored exactly like that and
225
+ // the field looks broken to the user who typed it. The attribute alone clears the other
226
+ // rule, which is how a form ends up with the attribute and no normalization at all.
227
+ // Same person-name token set (see there for why `name`/`fullname` are excluded).
228
+ pattern: `autocomplete=\\{?["'](?:name|given-name|family-name|additional-name|honorific-prefix)["']|(?:\\bname|\\bid|\\bfor|formControlName)=\\{?["'](?:first[-_]?name|last[-_]?name|given[-_]?name|family[-_]?name|surname|apellidos?)["']`,
229
+ absent: "capitalizeWords|capitalizeName|capitalizeEach|toTitleCase|titleCase|startCase|properCase|normalizeName|normalizePerson|capitalize\\(|charAt\\(0\\)\\.toUpperCase|enigma:allow-no-capitalize",
230
+ message: 'Person-name field with no value normalization. `autocapitalize="words"` only shapes the phone keyboard - a physical keyboard ignores it, so "juan perez" is stored verbatim. Normalize the VALUE with the shared normalizer (validation-policy): trim, collapse inner spaces, and uppercase the first letter of every word, ONLY that letter, so `McDonald`, `O\'Brien` and `van der Berg` survive. Run it on blur (never on every keystroke - it moves the caret and breaks IME composition) and again on the server, which is the copy that decides what is stored. Put it in the shared Input/TextField so the next form gets it by construction. For a field that must keep exactly what was typed, add an `enigma:allow-no-capitalize` note (validation-policy, frontend-policy).',
231
+ severity: "block",
232
+ skill: "validation-policy"
233
+ },
234
+ {
235
+ id: "sec-password-breach-check",
236
+ label: "A new password is checked against the breach corpus",
237
+ files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.html", "*.htm", "*.ts", "*.js"],
238
+ excludeFiles: [
239
+ "*.test.*",
240
+ "*.spec.*",
241
+ "**/tests/**",
242
+ "**/__tests__/**",
243
+ "**/stories/**",
244
+ "*.stories.*",
245
+ "*.min.js",
246
+ "**/dist/**",
247
+ "**/build/**",
248
+ "**/_build/**",
249
+ "**/node_modules/**",
250
+ "**/vendor/**",
251
+ "dist/**",
252
+ "build/**",
253
+ "_build/**",
254
+ "node_modules/**",
255
+ "vendor/**"
256
+ ],
257
+ scope: "file",
258
+ // `autocomplete="new-password"` is the spec's own marker for a password being CREATED -
259
+ // sign-up, reset confirmation, change password - and never appears on a sign-in form
260
+ // (that one is `current-password`). So it selects exactly the screens where the check
261
+ // belongs, with no path guessing. Any mention of the check anywhere in the file clears
262
+ // it, including a call into a shared hook whose name carries `pwned`/`breach`.
263
+ pattern: `autocomplete=\\{?["']new-password["']`,
264
+ absent: "pwnedpasswords|haveibeenpwned|hibp|pwned|breach|enigma:allow-no-breach-check",
265
+ 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).",
266
+ severity: "block",
267
+ skill: "security-policy"
268
+ },
269
+ {
270
+ id: "sec-password-identity-match",
271
+ label: "A new password is not the account's own identity",
272
+ files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.html", "*.htm", "*.ts", "*.js"],
273
+ excludeFiles: [
274
+ "*.test.*",
275
+ "*.spec.*",
276
+ "**/tests/**",
277
+ "**/__tests__/**",
278
+ "**/stories/**",
279
+ "*.stories.*",
280
+ "*.min.js",
281
+ "**/dist/**",
282
+ "**/build/**",
283
+ "**/_build/**",
284
+ "**/node_modules/**",
285
+ "**/vendor/**",
286
+ "dist/**",
287
+ "build/**",
288
+ "_build/**",
289
+ "node_modules/**",
290
+ "vendor/**"
291
+ ],
292
+ scope: "file",
293
+ // The twin of sec-password-breach-check over the same selector, for the same reason the
294
+ // name rules are two: one `absent` cannot express "breach check AND identity check", and
295
+ // a file that does one is routinely missing the other. Same precision inheritance -
296
+ // `autocomplete="new-password"` marks a password being CREATED and nothing else.
297
+ // The `absent` set is deliberately NOT `email|username`: every sign-up form on earth
298
+ // mentions both, so keying on them would clear the rule everywhere it matters. It clears
299
+ // only on evidence of a COMPARISON - zxcvbn fed the user's own inputs (advisory, but a
300
+ // form gating on its score is a real implementation), Django's similarity validator, a
301
+ // helper named for the check, or password and an identifier on the same line either side
302
+ // of an equality/containment operator.
303
+ pattern: `autocomplete=\\{?["']new-password["']`,
304
+ 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",
305
+ 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).",
306
+ severity: "block",
307
+ skill: "security-policy"
308
+ },
309
+ // NOTE: no rule for the navigation conventions - nav entries carrying icons, a long nav
310
+ // grouped into labelled sections, and a Cmd/Ctrl+K command palette once the app has enough
311
+ // to hunt through. All three were measured and rejected; they live in frontend-policy's
312
+ // "Navigation Is Structured, Not A Growing List" and "Search & Filtering" sections only.
313
+ // The signature would have to be DENSITY - a file rendering many destinations - and density
314
+ // does not separate the app shell (where these belong) from a landing page or a docs page
315
+ // (where an icon per link and a palette would both be wrong). Measured over the corpus:
316
+ // 174 UI files, 29 with 8 or more links, and of the 4 with no search affordance every one
317
+ // is marketing or static docs - zero true positives, the same evidence that rejected the
318
+ // no-op-save and pinned-sidebar rules. Keying on a *sidebar*/*nav* filename instead found
319
+ // 2 candidates in the whole corpus, both terminal (ink) menus, so it has no signal either.
320
+ {
321
+ id: "fe-tracking-before-consent",
322
+ label: "Non-essential tracking waits for consent",
323
+ files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.html", "*.htm", "*.ts", "*.js", "*.mts", "*.cts"],
324
+ excludeFiles: [
325
+ "*.test.*",
326
+ "*.spec.*",
327
+ "**/tests/**",
328
+ "**/__tests__/**",
329
+ "**/stories/**",
330
+ "*.stories.*",
331
+ "*.min.js",
332
+ "**/dist/**",
333
+ "**/build/**",
334
+ "**/_build/**",
335
+ "**/node_modules/**",
336
+ "**/vendor/**",
337
+ "dist/**",
338
+ "build/**",
339
+ "_build/**",
340
+ "node_modules/**",
341
+ "vendor/**"
342
+ ],
343
+ scope: "file",
344
+ // The loaders and call sites of the common analytics/ads/replay vendors. Each one sets
345
+ // non-essential storage the moment it runs, so what matters is whether ANY consent
346
+ // handling exists in the same file - the gate, a Consent Mode default, or the stored
347
+ // decision being read. `consent` on its own clears it, deliberately generous: this rule
348
+ // catches the snippet pasted straight into the layout, not a considered implementation.
349
+ pattern: "googletagmanager\\.com|google-analytics\\.com|gtag\\(|dataLayer\\.push|connect\\.facebook\\.net|fbq\\(|mixpanel\\.|posthog\\.(?:init|capture)|amplitude\\.(?:init|getInstance)|static\\.hotjar\\.com|clarity\\.ms|cdn\\.segment\\.com",
350
+ absent: "consent|gdpr|cookieBanner|cookie-banner|CookieConsent|enigma:allow-no-consent",
351
+ message: "Analytics, ads or session replay loading with no consent gate in sight. Everything outside the strictly necessary group (session, CSRF, load balancing, the consent record) stays off until the user answers the banner - swapping the cookie for `localStorage` does not change that. Load the vendor only after the stored decision says so (or start in Consent Mode with everything denied and update on accept), make Reject as reachable as Accept, and keep the decision withdrawable. If this file genuinely runs after the gate, add an `enigma:allow-no-consent` note (security-policy, frontend-policy).",
352
+ severity: "block",
353
+ skill: "security-policy"
354
+ },
198
355
  {
199
356
  id: "fe-no-native-dialog",
200
357
  label: "No native browser dialogs",
@@ -856,6 +1013,43 @@ var PROJECT_CHECKS = {
856
1013
  var FILE_CHECKS = {
857
1014
  "proc-windows-hide": (content) => missingWindowsHide(content)
858
1015
  };
1016
+ var FIXERS = {
1017
+ "fe-name-input-capitalize": (line, file) => {
1018
+ if (/autocapitalize/i.test(line)) return null;
1019
+ const tags = line.match(/<input\b/gi);
1020
+ if (!tags || tags.length !== 1) return null;
1021
+ const attr = /\.[jt]sx$/i.test(file) ? 'autoCapitalize="words"' : 'autocapitalize="words"';
1022
+ return line.replace(/<input\b/i, `<input ${attr}`);
1023
+ }
1024
+ };
1025
+ function applyFixes(file, findings) {
1026
+ const fixable = findings.filter((f) => f.line && FIXERS[f.ruleId]);
1027
+ if (!fixable.length) return { fixed: [], remaining: findings };
1028
+ let content;
1029
+ try {
1030
+ content = readFileSync(file, "utf8");
1031
+ } catch {
1032
+ return { fixed: [], remaining: findings };
1033
+ }
1034
+ const lines = content.split("\n");
1035
+ const fixed = [];
1036
+ for (const f of fixable) {
1037
+ const idx = f.line - 1;
1038
+ const before = lines[idx];
1039
+ if (before === void 0) continue;
1040
+ const after = FIXERS[f.ruleId](before, file);
1041
+ if (after === null || after === before) continue;
1042
+ lines[idx] = after;
1043
+ fixed.push(f);
1044
+ }
1045
+ if (!fixed.length) return { fixed: [], remaining: findings };
1046
+ try {
1047
+ writeFileSync(file, lines.join("\n"));
1048
+ } catch {
1049
+ return { fixed: [], remaining: findings };
1050
+ }
1051
+ return { fixed, remaining: checkPath(file) };
1052
+ }
859
1053
  var SPAWNERS = /* @__PURE__ */ new Set(["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"]);
860
1054
  function spawnerBindings(content) {
861
1055
  const names = /* @__PURE__ */ new Set();
@@ -1023,7 +1217,12 @@ function runGuardrailsHook(payload) {
1023
1217
  } catch {
1024
1218
  }
1025
1219
  if (!file || typeof file !== "string") return 0;
1026
- const findings = checkPath(file);
1220
+ const found = checkPath(file);
1221
+ if (!found.length) return 0;
1222
+ const { fixed, remaining: findings } = applyFixes(file, found);
1223
+ if (fixed.length) process.stdout.write(`enigma guardrails (fixed)
1224
+ ${fixed.map((f) => `${f.file}:${f.line} (${f.ruleId})`).join("\n")}
1225
+ `);
1027
1226
  if (!findings.length) return 0;
1028
1227
  const warns = findings.filter((f) => f.severity === "warn");
1029
1228
  const blocks = findings.filter((f) => f.severity === "block");
@@ -1093,7 +1292,9 @@ if (isGrEntry && fileURLToPath(import.meta.url) === grEntry) {
1093
1292
  export {
1094
1293
  BUILTIN_RULES,
1095
1294
  FILE_CHECKS,
1295
+ FIXERS,
1096
1296
  PROJECT_CHECKS,
1297
+ applyFixes,
1097
1298
  checkFile,
1098
1299
  checkPath,
1099
1300
  findProjectRoot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enigma-cli",
3
- "version": "1.33.1",
3
+ "version": "1.33.3",
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": {