enigma-cli 1.32.1 → 1.32.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 (32) hide show
  1. package/assets/memory/AGENTS.md +2 -0
  2. package/assets/memory/CLAUDE.md +2 -0
  3. package/assets/skills/anti-overengineering-policy/skill.json +1 -1
  4. package/assets/skills/anti-overengineering-review/skill.json +1 -1
  5. package/assets/skills/backend-policy/skill.json +1 -1
  6. package/assets/skills/ciphera-style-policy/skill.json +2 -2
  7. package/assets/skills/code-review-policy/skill.json +1 -1
  8. package/assets/skills/core-engineering-policy/SKILL.md +2 -0
  9. package/assets/skills/core-engineering-policy/skill.json +4 -4
  10. package/assets/skills/database-expert/skill.json +1 -1
  11. package/assets/skills/debugging-policy/skill.json +1 -1
  12. package/assets/skills/dependency-policy/skill.json +1 -1
  13. package/assets/skills/email-policy/SKILL.md +95 -0
  14. package/assets/skills/email-policy/skill.json +9 -0
  15. package/assets/skills/frontend-design/skill.json +1 -1
  16. package/assets/skills/frontend-policy/SKILL.md +24 -0
  17. package/assets/skills/frontend-policy/skill.json +3 -3
  18. package/assets/skills/git-policy/skill.json +1 -1
  19. package/assets/skills/logo-sourcing-policy/skill.json +1 -1
  20. package/assets/skills/security-policy/skill.json +1 -1
  21. package/assets/skills/skill-creator/skill.json +1 -1
  22. package/assets/skills/task-completion-policy/SKILL.md +3 -1
  23. package/assets/skills/task-completion-policy/skill.json +4 -4
  24. package/assets/skills/technical-writing-policy/skill.json +1 -1
  25. package/assets/skills/testing-policy/skill.json +1 -1
  26. package/assets/skills/validation-policy/skill.json +1 -1
  27. package/bin/checksums.json +4 -4
  28. package/bin/enigma.mjs +1 -0
  29. package/dist/guard.js +0 -0
  30. package/dist/guardrails.js +91 -1
  31. package/dist/trim.js +212 -0
  32. package/package.json +3 -2
@@ -14,6 +14,7 @@
14
14
  - anti-overengineering-policy: writing or refactoring implementation code, or any "be lazy"/"simplify"/over-engineering request - the YAGNI ladder and minimal-code discipline.
15
15
  - anti-overengineering-review: on-demand "what can we delete"/audit/over-engineering review or an enigma: debt-marker ledger - lists cuts, applies nothing.
16
16
  - backend-policy, frontend-policy, database-expert, validation-policy: server, client, persistence, and input-validation work.
17
+ - email-policy: sending or templating email from the server - React Email instead of hand-written HTML, plain-text alternative, link safety, and deliverability (SPF/DKIM/DMARC, bounce suppression, unsubscribe).
17
18
  - security-policy: secrets, auth, permissions, crypto, untrusted/tool output, and AI-agent/MCP/tool-use safety.
18
19
  - dependency-policy: adding/upgrading/auditing dependencies, lockfiles, and supply-chain risk.
19
20
  - testing-policy, code-review-policy, debugging-policy, git-policy: tests and test-suite layout (structured subfolders, never a flat tests/ dump), pre-delivery review, debugging, and commits/PRs.
@@ -48,6 +49,7 @@ Non-negotiable, language-agnostic defaults - apply them by default without being
48
49
  - For long or complex tasks - or any task you judge to warrant it - break the work into smaller, well-scoped subtasks and complete them incrementally, validating each subtask before moving to the next. Map the dependencies between subtasks first, and do only the decomposition the task genuinely needs - never over-decompose simple work.
49
50
  - For multi-item work (ports, migrations, batch changes), enumerate the FULL inventory of work units with deterministic commands before implementing, persist it as a checklist (file or todo system), and mark a unit done only after verifying it - never because a similar unit worked. This is the task-completion-policy skill; load it for any task that spans many files/items or bundles several asks.
50
51
  - "Pending", "pendiente", "TODO", "left as a follow-up", "next step: ...", or "you can do X yourself" is NOT an acceptable way to end a turn for work you are able to perform now. Do that work in this same turn. The only reasons to stop short are a genuine blocker - missing credentials or access, an irreversible or destructive choice, a business decision, or something the user explicitly approved deferring - and then you must name the blocker explicitly, never leave the item silently unfinished.
52
+ - Never end a turn asking permission to continue with work that was already asked for - "shall I continue with 5-8?", "¿sigo con las tareas 5-8 en este orden, o prefieres otro?", "do you want me to keep going?", "which should I do first?". The answer is always yes, so asking only costs the user a turn to say it: pick the most sensible order yourself and keep working until everything is done. Order, sequencing and priority among requested items are YOUR judgment calls, not the user's. Stop and ask only for a genuine blocker - access or credentials you lack, an irreversible or destructive action, a decision that is genuinely the user's (business, legal, cost) - and then NAME the blocker and what you finished before it, instead of asking whether to proceed. Resolve real ambiguity before starting, never as a way to pause mid-task. This one is enforced, not advisory: `enigma verify` denies the stop on a turn that ends by asking to continue.
51
53
  - Do not stop early because a task is long, tedious, or the context is filling up. Keep going until every enumerated item is finished or truly blocked. If work is genuinely paused, the checklist holds the remaining items - on resume, re-read it FIRST and continue from it; never reconstruct progress from memory, that is where items get dropped.
52
54
  - Never declare a task complete while any item is pending, stubbed, or unverified. Before saying "done": reconcile against the checklist, build/typecheck the whole artifact, and run `enigma verify` - it checks what you actually produced for unfinished work and runs the project's verification command. For a port, clone, or migration also run `enigma verify parity <source> <target>`, which reports any module that was never carried over. If anything remains, say exactly what remains instead of rounding up to "done". Never silently skip or stub an item - record it with a reason and report it.
53
55
  - Implement what was asked at the difficulty it actually has. Never quietly substitute a simplified stand-in because the real thing is tedious or hard - no regex where a real parser is required, no hardcoded special case where the general logic was asked for, no empty module, no "equivalent for now". If a faithful implementation is genuinely impossible here, say so explicitly and say why; downgrading it silently and then reporting success is the single worst outcome.
@@ -14,6 +14,7 @@
14
14
  - anti-overengineering-policy: writing or refactoring implementation code, or any "be lazy"/"simplify"/over-engineering request - the YAGNI ladder and minimal-code discipline.
15
15
  - anti-overengineering-review: on-demand "what can we delete"/audit/over-engineering review or an enigma: debt-marker ledger - lists cuts, applies nothing.
16
16
  - backend-policy, frontend-policy, database-expert, validation-policy: server, client, persistence, and input-validation work.
17
+ - email-policy: sending or templating email from the server - React Email instead of hand-written HTML, plain-text alternative, link safety, and deliverability (SPF/DKIM/DMARC, bounce suppression, unsubscribe).
17
18
  - security-policy: secrets, auth, permissions, crypto, untrusted/tool output, and AI-agent/MCP/tool-use safety.
18
19
  - dependency-policy: adding/upgrading/auditing dependencies, lockfiles, and supply-chain risk.
19
20
  - testing-policy, code-review-policy, debugging-policy, git-policy: tests and test-suite layout (structured subfolders, never a flat tests/ dump), pre-delivery review, debugging, and commits/PRs.
@@ -48,6 +49,7 @@ Non-negotiable, language-agnostic defaults - apply them by default without being
48
49
  - For long or complex tasks - or any task you judge to warrant it - break the work into smaller, well-scoped subtasks and complete them incrementally, validating each subtask before moving to the next. Map the dependencies between subtasks first, and do only the decomposition the task genuinely needs - never over-decompose simple work.
49
50
  - For multi-item work (ports, migrations, batch changes), enumerate the FULL inventory of work units with deterministic commands before implementing, persist it as a checklist (file or todo system), and mark a unit done only after verifying it - never because a similar unit worked. This is the task-completion-policy skill; load it for any task that spans many files/items or bundles several asks.
50
51
  - "Pending", "pendiente", "TODO", "left as a follow-up", "next step: ...", or "you can do X yourself" is NOT an acceptable way to end a turn for work you are able to perform now. Do that work in this same turn. The only reasons to stop short are a genuine blocker - missing credentials or access, an irreversible or destructive choice, a business decision, or something the user explicitly approved deferring - and then you must name the blocker explicitly, never leave the item silently unfinished.
52
+ - Never end a turn asking permission to continue with work that was already asked for - "shall I continue with 5-8?", "¿sigo con las tareas 5-8 en este orden, o prefieres otro?", "do you want me to keep going?", "which should I do first?". The answer is always yes, so asking only costs the user a turn to say it: pick the most sensible order yourself and keep working until everything is done. Order, sequencing and priority among requested items are YOUR judgment calls, not the user's. Stop and ask only for a genuine blocker - access or credentials you lack, an irreversible or destructive action, a decision that is genuinely the user's (business, legal, cost) - and then NAME the blocker and what you finished before it, instead of asking whether to proceed. Resolve real ambiguity before starting, never as a way to pause mid-task. This one is enforced, not advisory: `enigma verify` denies the stop on a turn that ends by asking to continue.
51
53
  - Do not stop early because a task is long, tedious, or the context is filling up. Keep going until every enumerated item is finished or truly blocked. If work is genuinely paused, the checklist holds the remaining items - on resume, re-read it FIRST and continue from it; never reconstruct progress from memory, that is where items get dropped.
52
54
  - Never declare a task complete while any item is pending, stubbed, or unverified. Before saying "done": reconcile against the checklist, build/typecheck the whole artifact, and run `enigma verify` - it checks what you actually produced for unfinished work and runs the project's verification command. For a port, clone, or migration also run `enigma verify parity <source> <target>`, which reports any module that was never carried over. If anything remains, say exactly what remains instead of rounding up to "done". Never silently skip or stub an item - record it with a reason and report it.
53
55
  - Implement what was asked at the difficulty it actually has. Never quietly substitute a simplified stand-in because the real thing is tedious or hard - no regex where a real parser is required, no hardcoded special case where the general logic was asked for, no empty module, no "equivalent for now". If a faithful implementation is genuinely impossible here, say so explicitly and say why; downgrading it silently and then reporting success is the single worst outcome.
@@ -7,6 +7,6 @@
7
7
  "minimalCode"
8
8
  ],
9
9
  "updated": "2026-07-17T00:17:02+02:00",
10
- "cliVersion": "1.32.1",
10
+ "cliVersion": "1.32.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.32.1",
7
+ "cliVersion": "1.32.3",
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, API and request optimization (batching, avoiding redundant calls, skipping no-op writes), server-side caching (Redis), and Zod boundary validation.",
6
6
  "updated": "2026-07-23T04:41:39+02:00",
7
- "cliVersion": "1.32.1",
7
+ "cliVersion": "1.32.3",
8
8
  "sha": "d091e8708888d23ee14cdd6c0e71f386861f2252d95360895a31c73db090a689"
9
9
  }
@@ -3,7 +3,7 @@
3
3
  "version": "1.2.0",
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
- "updated": "2026-07-30T15:47:21+02:00",
7
- "cliVersion": "1.32.1",
6
+ "updated": "2026-07-30T15:48:04+02:00",
7
+ "cliVersion": "1.32.3",
8
8
  "sha": "05dc812da459071110d96ee41c5201b1ee230471d635352415e10ef8b220c267"
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.32.1",
7
+ "cliVersion": "1.32.3",
8
8
  "sha": "3d3bbe0602d5bbb4afe37648fe3c2fa39376b1bcbac5d8c441f01fad1e866ed0"
9
9
  }
@@ -33,6 +33,7 @@ description: Highest-authority engineering rules - priority hierarchy, modular a
33
33
  - Any dependency added/upgraded/removed/audited, or package manifest/lockfile change -> dependency-policy.
34
34
  - Any UI, client state, data fetching, or client caching -> frontend-policy.
35
35
  - Any API endpoint, service, controller, or server request flow -> backend-policy.
36
+ - Any email sent or templated from the server (verification, password reset, receipts, alerts, digests) or any email-provider integration -> email-policy.
36
37
  - Any new or changed code that needs verification, or any test file created/moved/renamed or a test suite scaffolded/restructured -> testing-policy.
37
38
  - Any source code written, refactored, or reviewed (formatting, naming, language idioms) -> ciphera-style-policy.
38
39
  - Any implementation code being written or refactored, or any "be lazy"/"simplify"/over-engineering request -> anti-overengineering-policy.
@@ -83,6 +84,7 @@ This core policy owns orchestration, architecture, and the global rules. Each co
83
84
  - validation-policy: strict frontend + backend schema validation (Zod), schema consistency, client-facing error handling.
84
85
  - frontend-policy: frontend structure, reusable components, abstraction threshold, client-side caching, optimistic UI and rollback.
85
86
  - backend-policy: API/service architecture, controller-service-repository layering, API/request optimization, server-side caching (Redis).
87
+ - email-policy: transactional and notification email - React Email templating instead of hand-written HTML, server-side rendering, a single send module behind the provider SDK, plain-text alternatives, idempotent background sending, link safety, and deliverability (SPF/DKIM/DMARC, bounce suppression, unsubscribe).
86
88
  - testing-policy: test strategy, coverage gates, deterministic tests, test/regression-first discipline, and test-suite layout (structure by type and domain, mirrored source paths, file naming, fixture/helper/factory placement).
87
89
  - code-review-policy: self-review before delivery, review dimensions, change-quality gates.
88
90
  - debugging-policy: reproduce-isolate-fix methodology and root-cause discipline.
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "core-engineering-policy",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Core engineering execution policy and harness orchestration (highest-authority rules).",
6
- "updated": "2026-07-16T22:43:53+02:00",
7
- "cliVersion": "1.32.1",
8
- "sha": "d132f20db08806054d95e58d3d56c2aa7ebc8e6dd24d902b0a0ed9ddfae216c5"
6
+ "updated": "2026-07-30T19:29:19+02:00",
7
+ "cliVersion": "1.32.3",
8
+ "sha": "6a881d8589926fa7f48058314fd26d7042fd2ac82f1c87a6c11ffb54d1fda22b"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Senior database architecture policy: query optimization, anti-duplication/normalization, scalability, and RGPD/GDPR encryption.",
6
6
  "updated": "2026-06-03T14:19:50+02:00",
7
- "cliVersion": "1.32.1",
7
+ "cliVersion": "1.32.3",
8
8
  "sha": "2883bcecb3202683ae6f81b073c3d6a9cec9c55029e011bdd06ba7ac3537297e"
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.32.1",
7
+ "cliVersion": "1.32.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.32.1",
7
+ "cliVersion": "1.32.3",
8
8
  "sha": "6375d835c2aef2c9bd31ce116444dc3d796f510f9970a213aa3ac4696d7e21b9"
9
9
  }
@@ -0,0 +1,95 @@
1
+ ---
2
+ name: email-policy
3
+ description: Transactional and notification email - build templates with React Email (react.email) instead of hand-written HTML tables or string concatenation, render them server-side, send through the provider SDK behind a single send module, and apply deliverability (SPF/DKIM/DMARC, bounce and complaint suppression, unsubscribe headers), plain-text alternatives, idempotent background sending, and link safety. Use when sending, templating, styling, previewing, or testing any email from a backend, API route, cron job, worker, or CLI - signup and verification, password reset, receipts, alerts, digests, or any provider integration (Resend, Postmark, SendGrid, AWS SES, Mailgun, Nodemailer/SMTP).
4
+ ---
5
+
6
+ # Email Policy (Transactional & Notification Mail)
7
+
8
+ ## Activation Scope
9
+
10
+ - Apply whenever code sends an email or builds an email body: verification, password reset, invites, receipts, alerts, digests, notifications, or any email-provider integration.
11
+ - Owns templating, rendering, sending, and deliverability. Layering, retries, and queues stay with backend-policy; secrets, tokens, and link safety with security-policy; recipient and payload schemas with validation-policy; package choice with dependency-policy.
12
+
13
+ ---
14
+
15
+ ## Never Hand-Write Email HTML
16
+
17
+ Email clients are roughly two decades behind browsers: the Outlook desktop engine renders with Word, Gmail rewrites and strips `<style>`, external CSS never loads, and flexbox/grid are unavailable. HTML assembled by hand or from template strings looks correct in a browser preview and breaks in the actual inbox - and string-concatenated user values in markup are an injection surface.
18
+
19
+ - Do NOT: build bodies with template literals or Handlebars-into-tables, copy a random table skeleton off a blog, or reuse an app React component as an email.
20
+ - Do NOT judge an email by a browser screenshot - a browser is the one client that renders everything.
21
+ - DO: use a compiler that targets email HTML. In JavaScript/TypeScript that is React Email.
22
+
23
+ ---
24
+
25
+ ## React Email (default for JS/TS)
26
+
27
+ https://react.email - MIT, maintained by the Resend team. React components that compile to email-safe HTML, tested across Gmail, Apple Mail, Outlook, Yahoo! Mail, HEY, and Superhuman.
28
+
29
+ ### Packages
30
+
31
+ - `@react-email/components` - the component set (peer: React 18 or 19): `Html`, `Head`, `Body`, `Container`, `Section`, `Row`, `Column`, `Heading`, `Text`, `Link`, `Button`, `Img`, `Hr`, `Preview`, `Font`, `Markdown`, `CodeBlock`, `CodeInline`, `Tailwind`.
32
+ - `@react-email/render` - the runtime renderer: `render()`, `pretty()`, `toPlainText()`. This is the only piece production needs, so it belongs in `dependencies`.
33
+ - `react-email` - the local preview server (`email dev`, bin `email`) that also re-exports `render`. It pulls in esbuild, socket.io, tailwindcss, prismjs, and chokidar: keep it a **devDependency**. Do not add it to production dependencies just to import `render` (dependency-policy, anti-overengineering-policy).
34
+
35
+ ### Render, then send
36
+
37
+ ```tsx
38
+ import { render } from "@react-email/render";
39
+ import { WelcomeEmail } from "@/emails/welcome";
40
+
41
+ const html = await render(<WelcomeEmail name={user.name} verifyUrl={url} />);
42
+ const text = await render(<WelcomeEmail name={user.name} verifyUrl={url} />, { plainText: true });
43
+ ```
44
+
45
+ - `render()` is asynchronous - await it. Options: `plainText`, `pretty`, `htmlToTextOptions`. `toPlainText(html)` converts an already-rendered body, and `data-skip-in-text="true"` drops an element from the text version only.
46
+ - Templates live in one folder (`emails/`), one file per template, props typed. They are ordinary React components, so the reuse rules in frontend-policy apply: one shared layout/branding wrapper composed by every template, never a copied header per email.
47
+ - Style with the `<Tailwind>` wrapper when the project already uses Tailwind, otherwise inline `style` props. Never link an external stylesheet or a CDN webfont - clients strip or block them (use `<Font>` with a fallback stack).
48
+ - Always include `<Preview>`; without it the inbox preview line shows whatever stray text comes first.
49
+ - Host images on an absolute HTTPS URL and always set `alt` - most clients block images by default, so an email whose meaning lives only in an image arrives empty.
50
+ - Preview with `email dev` (or a rendered fixture) before shipping.
51
+
52
+ ### Boundaries
53
+
54
+ - Non-React backends: use MJML or the provider's own template system, under the same doctrine - never hand-rolled tables.
55
+ - Marketing and campaign email belongs in the marketing platform, not in application code. This policy covers transactional and notification mail.
56
+
57
+ ---
58
+
59
+ ## Sending
60
+
61
+ - One module owns sending (e.g. `lib/email/send.ts`): it takes `{to, subject, template, props}`, renders HTML plus text, calls the provider, and returns a typed result. Feature code never imports the provider SDK directly - that keeps the provider swappable and concentrates logging, suppression, and idempotency in one place (backend-policy).
62
+ - Prefer the provider's official SDK or HTTP API over raw SMTP when one exists; React Email documents integrations for Resend, Postmark, SendGrid, AWS SES, Mailgun, MailerSend, Plunk, Scaleway, Azure Communication Email, and Nodemailer.
63
+ - API keys come from the environment or a secret store, server-side only - never in a client bundle, never committed (security-policy).
64
+ - Always send HTML **and** a plain-text alternative. Text-only clients, accessibility tooling, and spam filters all read it.
65
+ - Validate the recipient address and the template payload against a schema before rendering (validation-policy).
66
+ - Never block the user's response on the provider. Enqueue the send and return; a slow or failing mail provider must not fail the request. Where the project has no queue, send after the response with a timeout and log the failure instead of propagating it - unless the email IS the operation the user asked for.
67
+ - Make sends idempotent: derive a key from the event (user id + template + event id) so a retried job or a double-submitted request does not send twice, and pass the provider's idempotency key when it offers one. Retries are bounded with backoff (backend-policy).
68
+
69
+ ---
70
+
71
+ ## Content & Link Safety
72
+
73
+ - Never put a password, a secret, an API key, a full session token, or sensitive personal data in an email body. Mail sits in plaintext in most mailboxes and is forwarded freely. Send a short-lived, single-use link instead, and expire it on use.
74
+ - Build action links from a server-side base URL constant, never from the request `Host` header - header poisoning turns a password-reset email into account takeover.
75
+ - Validate any redirect or callback target against an allowlist before embedding it.
76
+ - React escapes text by default; the remaining risks are attributes and raw HTML. Validate every `href`/`src` against an `http`/`https` scheme allowlist, and never feed unsanitized user input to `<Markdown>` or a dangerouslySetInnerHTML equivalent (security-policy).
77
+
78
+ ---
79
+
80
+ ## Deliverability
81
+
82
+ - Send from a verified domain with SPF, DKIM, and DMARC configured. An unauthenticated domain lands in spam no matter how good the content is.
83
+ - Use a real, monitored `Reply-To`. Avoid `noreply@` for mail a human may reasonably answer.
84
+ - Consume the provider's bounce and complaint webhooks: suppress hard bounces and spam complaints permanently in your own store, and check that suppression list before every send. Ignoring it burns domain reputation for every other email you send.
85
+ - Any bulk or recurring email (digest, newsletter, activity summary) needs a working unsubscribe: `List-Unsubscribe` and `List-Unsubscribe-Post` headers plus a visible link, honored immediately and without a login. Genuine transactional mail (receipt, password reset, security alert) is exempt - do not use that exemption to smuggle marketing.
86
+ - Respect the provider's rate limits and daily quota; queue and pace bulk sends rather than bursting into a 429.
87
+ - Do not enable open/click tracking by default. It is a privacy decision with GDPR/RGPD consequences, not a sensible default.
88
+
89
+ ---
90
+
91
+ ## Testing & Observability
92
+
93
+ - Test templates by rendering them, not by sending: assert on the rendered HTML and text (subject line, required links present, no unresolved placeholder, correct locale). Deterministic and offline (testing-policy).
94
+ - Never send real mail from tests or local development. Use the provider's sandbox or test key, or a local catcher (Mailpit, Mailhog, Ethereal).
95
+ - Log every attempt with template id, provider message id, and outcome - never the body, the token, or the link. Alert on bounce and complaint rates, not just on send errors.
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "email-policy",
3
+ "version": "1.0.0",
4
+ "provider": "FJRG2007/enigma",
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
+ "updated": "2026-07-30T19:29:19+02:00",
7
+ "cliVersion": "1.32.3",
8
+ "sha": "c9724fdbcdbeab99573be3fd44d4cdd97c2a394d99f3c4395f118f17356b00ed"
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.32.1",
7
+ "cliVersion": "1.32.3",
8
8
  "sha": "9e30ee7d8a1a1e8c6e7f4e043857cd01841c68a427752e45bc0cad9ec5cfa279"
9
9
  }
@@ -123,6 +123,30 @@ Build every UI to adapt to the viewport; never assume a desktop width. A layout
123
123
 
124
124
  ---
125
125
 
126
+ ## Persistent Chrome Stays Put (Sidebars, Nav, Panels)
127
+
128
+ Navigation and any other persistent chrome must stay where the user left it while the CONTENT scrolls. A sidebar that scrolls up and off the top with the page is one of the most common generated-layout defects, and it makes a long page unnavigable: the user has to scroll back up to reach any other link.
129
+
130
+ - Put the scroll on the content, not on the page. The reliable shape is a viewport-height shell whose sidebar and main are siblings, each owning its own overflow: `position: sticky` with a `top` offset (simplest - the sidebar stays in the grid and needs no width duplication), or a `position: fixed` sidebar with matching padding on the content. Tailwind's `sticky top-0` / `h-screen sticky` do the same thing.
131
+ - **Pinning alone is not enough, and this is the part that gets missed.** `position: sticky` only pins the element's TOP edge: as soon as the sidebar's own content is taller than the viewport, the rest keeps scrolling away with the page and the last entries can never be reached. A pinned sidebar therefore needs BOTH a height bounded by the viewport (`max-height: calc(100vh - <header height>)`, or `h-screen`) AND its own `overflow-y: auto`. A sidebar with a content-sized height (`height: max-content`, `fit-content`, or none at all) plus `overflow: visible` is the broken pattern, even though it looks correct until the list grows.
132
+ - Add `overscroll-behavior: contain` to the sidebar's scroll container so reaching its end does not start scrolling the page behind it.
133
+ - Undo it where the layout stacks. At the mobile breakpoint the sidebar usually returns to normal flow (`position: static`), and the viewport height cap has to be lifted with it (`max-height: none; overflow-y: visible`) or it turns into a small scrolling box inside the page.
134
+ - The same rule covers a sticky header, toolbar, filter rail or side panel: pinned, bounded, and independently scrollable. Keep pinned chrome shallow - it eats vertical space on small screens, so collapse it into a drawer or a top bar there rather than pinning it over half the viewport.
135
+ - Verify by scrolling to the BOTTOM of a long page and confirming the sidebar is still on screen with its last entry reachable. Mechanically: the sidebar's `getBoundingClientRect().top` stays at its offset as the page scrolls, and `scrollHeight <= clientHeight` holds for it, or it can scroll itself.
136
+
137
+ ---
138
+
139
+ ## Links In Copy Are Links
140
+
141
+ 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.
142
+
143
+ - A URL inside a sentence becomes an inline anchor on the words that name it, not a raw address dropped mid-paragraph: "Leave empty to use the [deployment default](https://example.com)" reads better than repeating the URL. Show the bare address only when the exact value is the information (a host to whitelist, an endpoint to paste elsewhere) - and then pair it with a copy button rather than expecting a manual selection.
144
+ - External links carry `target="_blank"` with `rel="noopener noreferrer"`, and say where they go when the destination is not obvious from the text. In-app destinations use the router, never a full page reload.
145
+ - Choose the affordance by weight, not by habit: an inline anchor for a reference inside a sentence, a button for the primary action of a panel or empty state ("Open dashboard"), an icon button where it repeats per row (Icon Actions above). A whole sentence is never a link - link the noun.
146
+ - The same applies to anything else with a natural affordance: a file path gets a copy button, an email gets `mailto:`, a referenced setting gets a link to that settings screen. If the copy tells the user to go somewhere, take them there.
147
+
148
+ ---
149
+
126
150
  ## Text That Does Not Fit (Variable-Length Content)
127
151
 
128
152
  Every string is variable-length; the value on screen during development is one sample. Text escaping its card or colliding with a neighbour is the most common layout defect, it is invisible until the content changes, and it is the responsibility of whoever writes the layout - not something to be pointed out afterwards.
@@ -3,7 +3,7 @@
3
3
  "version": "1.20.0",
4
4
  "provider": "FJRG2007/enigma",
5
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), 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-29T01:26:14+02:00",
7
- "cliVersion": "1.32.1",
8
- "sha": "71ebc71510c8aa54d7925445a7dffc55dbce9eba209fb6a63bae05d119217d79"
6
+ "updated": "2026-07-30T19:29:05+02:00",
7
+ "cliVersion": "1.32.3",
8
+ "sha": "a9750bd74505373aea8d9265515fc3b7f0fddf3e822d9e046248223e24ff96af"
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.32.1",
7
+ "cliVersion": "1.32.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.32.1",
10
+ "cliVersion": "1.32.3",
11
11
  "sha": "09cdbefd98625b02a7d03685e5deed128238ff8454a83fe22279610fe3ef8ddf"
12
12
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Application and AI-agent security: secrets, authn/authz (least privilege), OWASP Top 10, transport/crypto baseline, secure logging, and agent/MCP/tool-use safety.",
6
6
  "updated": "2026-06-01T00:45:28+02:00",
7
- "cliVersion": "1.32.1",
7
+ "cliVersion": "1.32.3",
8
8
  "sha": "9971e9d9127397d0152e89d24aad3191e2935e55a8483db7fd15f5d4d7a60e7a"
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.32.1",
7
+ "cliVersion": "1.32.3",
8
8
  "sha": "13d24c217bdb6fe83fe16835d8f5c3d397a9f3338f16876c61ef96e97f34c90a"
9
9
  }
@@ -35,6 +35,8 @@ description: Exhaustive completion discipline for long, complex, or multi-item t
35
35
 
36
36
  - Work unit by unit (or in coherent batches). Flip a unit to `done` only after its own verification - it compiles/typechecks, its tests pass, or a smoke check ran - never because a similar unit worked.
37
37
  - Never silently skip, stub, or simplify a unit. A stub, TODO, or partial implementation keeps the unit `pending` or `blocked` with the reason recorded. Schedule hard units early; difficulty is a reason to start sooner, not to defer.
38
+ - Never pause the run to ask which unit comes next or in what order. The ledger already answers that: order the units yourself (dependencies first, hard ones early) and keep working. "Shall I continue with the rest?", "¿sigo con las tareas 5-8, o prefieres otro orden?", "which should I start with?" are not checkpoints - they hand an unfinished task back to the user, who already asked for all of it, and cost a turn to answer something with one possible answer.
39
+ - The only legitimate pause is a genuine blocker: access or credentials you lack, an irreversible or destructive action, or a decision that is genuinely the user's (business, legal, cost). Record it as `blocked(<reason>)`, report the blocker by name along with everything finished before it, and never phrase it as a request for permission to keep going. Real ambiguity about scope is resolved BEFORE the inventory, not used to pause mid-run.
38
40
  - On context compaction, session resume, or sub-agent handback: re-read the ledger FIRST and continue from it. Never reconstruct progress from memory - that is where items get dropped.
39
41
  - Sub-agents must report which ledger units they completed and how each was verified; unverified claims stay `pending`.
40
42
 
@@ -53,7 +55,7 @@ A completion claim is forbidden unless ALL of these hold:
53
55
 
54
56
  - If any check fails, the task is NOT done: state exactly what remains and keep working (or report the blocker). Never say "everything is complete", "fully ported", or "all done" while the ledger has open units.
55
57
  - Words like "complete", "all", "every", and "fully" in a final report are claims that must be backed by checks 1-5.
56
- - These checks also run automatically at turn end: when a final message claims the work is finished, enigma re-runs them and denies the stop if the evidence contradicts the claim (see the verify concept). Treat that as a backstop for accidents, never as the thing that does the checking - a claim it has to catch was one that should never have been made.
58
+ - These checks also run automatically at turn end: when a final message claims the work is finished, enigma re-runs them and denies the stop if the evidence contradicts the claim - and it likewise denies a stop whose final message asks whether, or in which order, to continue (see the verify concept). Treat that as a backstop for accidents, never as the thing that does the checking - a claim it has to catch was one that should never have been made.
57
59
 
58
60
  ---
59
61
 
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "task-completion-policy",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Exhaustive completion discipline for long/multi-item tasks - inventory, coverage ledger, verified done.",
6
- "updated": "2026-07-27T17:22:17+02:00",
7
- "cliVersion": "1.32.1",
8
- "sha": "b9046c15fd636057a8e42e199d2eeb47297477f38a0a26a07a7b0bf71d61fcb4"
6
+ "updated": "2026-07-30T20:10:24+02:00",
7
+ "cliVersion": "1.32.3",
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.32.1",
7
+ "cliVersion": "1.32.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.32.1",
7
+ "cliVersion": "1.32.3",
8
8
  "sha": "3bdf591057b760f674fb2b1425f63acb426cda2c4f042e1a74c5a5d3807df664"
9
9
  }
@@ -4,6 +4,6 @@
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Strict frontend + backend schema validation, schema consistency, and safe client-facing error handling.",
6
6
  "updated": "2026-07-22T01:41:06+02:00",
7
- "cliVersion": "1.32.1",
7
+ "cliVersion": "1.32.3",
8
8
  "sha": "d937df0052d1ec151728a28f6567744d9e7ca0a65d84b4d5c9a697f1e40d704f"
9
9
  }
@@ -1,6 +1,6 @@
1
1
  {
2
- "enigma-darwin-arm64": "36ef9858fe95b1f4ed774ee94afa8f8d76679afa46a4255c73aefd6eefda0649",
3
- "enigma-linux-arm64": "3d5f6b79e5cae50cec321e4cbf0f592c18a9f30814d1f44bdecad5be2e860171",
4
- "enigma-linux-x64": "4f2669bc370e1a29b9e3b86a269a2349ea1a7534ec953a8acedd2e3ea1fc2c23",
5
- "enigma-win32-x64.exe": "4e2b6fd7078e7c0b8b99036534a91d4b8cec9fb64d00ac642a86f4ce272e48b1"
2
+ "enigma-darwin-arm64": "a007e45632f6817129b078f5c7fca70fa1f9e6a155048cb9869c8ea8ca272397",
3
+ "enigma-linux-arm64": "e69b0e0c6e5beddf8892d99cf8f9e1938b67396afc38db775fabec9400998cd7",
4
+ "enigma-linux-x64": "2c1690f89061f09501e1be55b51a2fe162262dfbb3d7398cc2b9921bdb644631",
5
+ "enigma-win32-x64.exe": "14a6632299edc52c442d4a007e96e17c9865d95a8fcf3a2e8da963017c620ef7"
6
6
  }
package/bin/enigma.mjs CHANGED
@@ -66,6 +66,7 @@ const env = { ...process.env };
66
66
  env.ENIGMA_ASSETS_DIR = join(pkgRoot, "assets");
67
67
  env.ENIGMA_GUARD_PATH = join(pkgRoot, "dist", "guard.js");
68
68
  env.ENIGMA_GUARDRAILS_PATH = join(pkgRoot, "dist", "guardrails.js");
69
+ env.ENIGMA_TRIM_PATH = join(pkgRoot, "dist", "trim.js");
69
70
  try {
70
71
  env.ENIGMA_VERSION = packageVersion();
71
72
  } catch { /* version is best-effort */ }
package/dist/guard.js CHANGED
Binary file
@@ -199,6 +199,20 @@ var BUILTIN_RULES = [
199
199
  severity: "warn",
200
200
  skill: "frontend-policy"
201
201
  },
202
+ // NOTE: there is deliberately no "a pinned sidebar needs its own scroll" rule, and no
203
+ // "a URL in UI copy must be a link" rule, though both conventions were asked for. Neither
204
+ // has a file-local signature this engine can read.
205
+ // - The sidebar defect is a RELATION between three declarations (pinned + height bound +
206
+ // overflow) that real stylesheets spread over several lines of one block, which a
207
+ // line-based scan cannot correlate; the Tailwind one-line form would be the exception,
208
+ // and the corpus (this repo, apps/web, references/repos) holds exactly two real
209
+ // sidebars, neither of them Tailwind - zero measured true positives, which is the same
210
+ // evidence that got the no-op-save rule rejected. It is also not a defect until the
211
+ // sidebar's content outgrows the viewport, so a rule would flag correct short ones.
212
+ // - The link one cannot tell COPY from DATA: a URL in a string is far more often an API
213
+ // endpoint, a default, or a docs reference in a comment than a piece of UI text, and
214
+ // nothing in the string says which. Both live in frontend-policy instead (Persistent
215
+ // Chrome Stays Put, Links In Copy Are Links).
202
216
  // NOTE: there is deliberately no "truncating flex item needs min-w-0" rule. It was written
203
217
  // and then removed after measuring it in a browser: per CSS Flexbox 4.5 a flex item's
204
218
  // automatic minimum size only applies while its computed overflow is visible, and Tailwind's
@@ -415,6 +429,40 @@ var BUILTIN_RULES = [
415
429
  message: 'Too many named bindings from one module. Import it as a namespace instead - `import * as <ns> from "<module>"`, then call `<ns>.thing()` - so the import stays one short line, each call site says where the symbol comes from, and a new export never widens the import again. The count sums every named import of that module in this file, so splitting the statement in two does not help; name the namespace for the module, and pick a distinct name when the natural one is already a local variable. Keep named imports for a handful of symbols. Mark a deliberate exception with an `enigma:` note on the import line (ciphera-style-policy).',
416
430
  severity: "block",
417
431
  skill: "ciphera-style-policy"
432
+ },
433
+ {
434
+ id: "proc-windows-hide",
435
+ label: "Spawned process must not pop a console window",
436
+ files: ["*.ts", "*.tsx", "*.mts", "*.cts", "*.js", "*.mjs", "*.cjs", "*.jsx"],
437
+ // Tests run in a terminal that already has a console, so a flashing window is not a
438
+ // defect there. Same two-form generated/vendored excludes as the rules above.
439
+ excludeFiles: [
440
+ "*.test.*",
441
+ "*.spec.*",
442
+ "**/tests/**",
443
+ "**/__tests__/**",
444
+ "**/fixtures/**",
445
+ "*.min.js",
446
+ "*.d.ts",
447
+ "**/dist/**",
448
+ "**/build/**",
449
+ "**/_build/**",
450
+ "**/node_modules/**",
451
+ "**/vendor/**",
452
+ "dist/**",
453
+ "build/**",
454
+ "_build/**",
455
+ "node_modules/**",
456
+ "vendor/**"
457
+ ],
458
+ scope: "file",
459
+ // A call spanning several lines has no line-regex form, hence a coded check (see
460
+ // missingWindowsHide for the three shapes it deliberately leaves alone). BLOCK for the
461
+ // ui-no-em-dash reason: a warn exits 0 and never reaches the model, the symptom is
462
+ // invisible to whoever writes the code on macOS or Linux, and the fix is one key.
463
+ fileCheck: "proc-windows-hide",
464
+ message: "Process spawned without windowsHide. On Windows a console child started by a process that has no console of its own - a daemon, an editor hook, a detached background task - pops a real console window on screen and closes it again, which reads as something crashing. Add `windowsHide: true` to the options object; it is inert on macOS and Linux, and inert on Windows when the parent already has a console, so it is safe on every call that is not deliberately opening a terminal for the user. For one that IS (a login flow that must show a terminal), mark the call with an `enigma:` note.",
465
+ severity: "block"
418
466
  }
419
467
  ];
420
468
  var PROJECT_CHECKS = {
@@ -428,6 +476,41 @@ var PROJECT_CHECKS = {
428
476
  return !("prisma" in pkg || "@prisma/client" in pkg);
429
477
  }
430
478
  };
479
+ var FILE_CHECKS = {
480
+ "proc-windows-hide": (content) => missingWindowsHide(content)
481
+ };
482
+ var SPAWNERS = /* @__PURE__ */ new Set(["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"]);
483
+ function spawnerBindings(content) {
484
+ const names = /* @__PURE__ */ new Set();
485
+ const stmt = /(?:import|(?:const|let|var))\s*\{([^}]*)\}\s*(?:from\s*|=\s*require\(\s*)["'](?:node:)?child_process["']/g;
486
+ for (const m of content.matchAll(stmt)) {
487
+ for (const part of m[1].split(",")) {
488
+ const [orig, alias] = part.trim().split(/\s+as\s+/).map((s) => s.trim());
489
+ if (orig && SPAWNERS.has(orig)) names.add(alias || orig);
490
+ }
491
+ }
492
+ return [...names];
493
+ }
494
+ function missingWindowsHide(content) {
495
+ const names = spawnerBindings(content);
496
+ if (names.length === 0) return [];
497
+ const out = [];
498
+ const call = new RegExp(`(?<![.\\w$])(${names.join("|")})\\s*\\(`, "g");
499
+ for (const m of content.matchAll(call)) {
500
+ let i = m.index + m[0].length;
501
+ for (let depth = 1; i < content.length && depth > 0; i++) {
502
+ if (content[i] === "(") depth++;
503
+ else if (content[i] === ")") depth--;
504
+ }
505
+ const eol = content.indexOf("\n", i);
506
+ const text = content.slice(m.index, eol === -1 ? content.length : eol);
507
+ if (/windowsHide|enigma:|"inherit"|'inherit'/.test(text)) continue;
508
+ if (!text.includes("{")) continue;
509
+ if (/\{[^{}]*\.\.\.[A-Za-z_$]/.test(text)) continue;
510
+ out.push({ line: content.slice(0, m.index).split("\n").length, detail: m[1] });
511
+ }
512
+ return out;
513
+ }
431
514
  var NAMED_IMPORT = /^import[ \t]+(?:[\w$]+[ \t]*,[ \t]*)?(?:type[ \t]+)?\{([^}]*)\}[ \t]*from[ \t]*["']([^"']+)["'].*$/gm;
432
515
  var INTERNAL_MODULE = /^\.|^#|^[@~]\//;
433
516
  function wideNamedImports(content, max) {
@@ -508,6 +591,11 @@ function checkFile(file, content, projectRoot) {
508
591
  for (const w of wideNamedImports(content, rule.maxNamedImports)) {
509
592
  out.push({ ...base, line: w.line, message: `${rule.message} (${w.count} bindings from "${w.module}", budget ${rule.maxNamedImports})` });
510
593
  }
594
+ } else if (rule.scope === "file" && rule.fileCheck) {
595
+ const check = FILE_CHECKS[rule.fileCheck];
596
+ for (const hit of check ? check(content) : []) {
597
+ out.push({ ...base, line: hit.line, message: `${rule.message} (${hit.detail})` });
598
+ }
511
599
  } else if (rule.scope === "file" && rule.pattern) {
512
600
  if (rule.absent) {
513
601
  try {
@@ -575,7 +663,7 @@ Fix the above before continuing.
575
663
  return 0;
576
664
  }
577
665
  function gitFiles(all) {
578
- const out = execFileSync("git", all ? ["ls-files"] : ["diff", "--cached", "--name-only", "--diff-filter=ACM"], { encoding: "utf8" });
666
+ const out = execFileSync("git", all ? ["ls-files"] : ["diff", "--cached", "--name-only", "--diff-filter=ACM"], { encoding: "utf8", windowsHide: true });
579
667
  return out.split("\n").map((s) => s.trim()).filter(Boolean);
580
668
  }
581
669
  function runGuardrailsScan(all) {
@@ -627,12 +715,14 @@ if (isGrEntry && fileURLToPath(import.meta.url) === grEntry) {
627
715
  }
628
716
  export {
629
717
  BUILTIN_RULES,
718
+ FILE_CHECKS,
630
719
  PROJECT_CHECKS,
631
720
  checkFile,
632
721
  checkPath,
633
722
  findProjectRoot,
634
723
  formatFindings,
635
724
  loadRules,
725
+ missingWindowsHide,
636
726
  runGuardrailsHook,
637
727
  runGuardrailsScan,
638
728
  runGuardrailsScanCli,
package/dist/trim.js ADDED
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/trim.ts
4
+ import { readFileSync } from "fs";
5
+ import { fileURLToPath } from "url";
6
+ import { extname, join } from "path";
7
+ import { open, stat } from "fs/promises";
8
+ import { execFileSync } from "child_process";
9
+ var TAIL_BYTES = 4096;
10
+ var MAX_TAIL = 1024 * 1024;
11
+ var CONCURRENCY = 32;
12
+ var SKIP_EXT = /* @__PURE__ */ new Set([
13
+ ".patch",
14
+ ".diff",
15
+ ".snap",
16
+ ".png",
17
+ ".jpg",
18
+ ".jpeg",
19
+ ".gif",
20
+ ".webp",
21
+ ".avif",
22
+ ".ico",
23
+ ".bmp",
24
+ ".tiff",
25
+ ".pdf",
26
+ ".zip",
27
+ ".gz",
28
+ ".tgz",
29
+ ".bz2",
30
+ ".xz",
31
+ ".7z",
32
+ ".rar",
33
+ ".jar",
34
+ ".woff",
35
+ ".woff2",
36
+ ".ttf",
37
+ ".otf",
38
+ ".eot",
39
+ ".mp3",
40
+ ".mp4",
41
+ ".wav",
42
+ ".ogg",
43
+ ".webm",
44
+ ".mov",
45
+ ".avi",
46
+ ".exe",
47
+ ".dll",
48
+ ".so",
49
+ ".dylib",
50
+ ".bin",
51
+ ".wasm",
52
+ ".node",
53
+ ".class",
54
+ ".pyc",
55
+ ".db",
56
+ ".sqlite",
57
+ ".sqlite3",
58
+ ".lock"
59
+ ]);
60
+ var SKIP_DIR = /(^|\/)(__snapshots__|fixtures|testdata|golden|node_modules|\.git|vendor|third_party|third-party|dist|build|coverage|\.next|\.venv)(\/|$)/;
61
+ function readIgnoreGlobs(root) {
62
+ try {
63
+ const raw = JSON.parse(readFileSync(join(root, ".githooks", "enigma-trim.json"), "utf8"));
64
+ return Array.isArray(raw.ignore) ? raw.ignore.filter((g) => typeof g === "string") : [];
65
+ } catch {
66
+ return [];
67
+ }
68
+ }
69
+ function globToRegExp(glob) {
70
+ const esc = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
71
+ const body = esc.replace(/\*\*/g, " ").replace(/\*/g, "[^/]*").replace(/ /g, ".*").replace(/\?/g, "[^/]");
72
+ return new RegExp(glob.includes("/") ? `^${body}$` : `(^|/)${body}$`);
73
+ }
74
+ var BLANK_LINE = /^[ \t\r]*$/;
75
+ function isSkipped(file) {
76
+ const norm = file.replace(/\\/g, "/");
77
+ return SKIP_DIR.test(norm) || SKIP_EXT.has(extname(norm).toLowerCase());
78
+ }
79
+ function trailingBlankBytes(tail, atStart) {
80
+ const lines = tail.split("\n");
81
+ if (lines.pop() !== "") return 0;
82
+ let bytes = 0;
83
+ for (let i = lines.length - 1; i >= 0; i--) {
84
+ const line = lines[i];
85
+ if (!BLANK_LINE.test(line)) return bytes;
86
+ bytes += line.length + 1;
87
+ }
88
+ return atStart ? 0 : -1;
89
+ }
90
+ async function trimFile(file) {
91
+ if (isSkipped(file)) return false;
92
+ let handle;
93
+ try {
94
+ const info = await stat(file);
95
+ if (!info.isFile() || info.size === 0) return false;
96
+ handle = await open(file, "r+");
97
+ let want = TAIL_BYTES;
98
+ for (; ; ) {
99
+ const length = Math.min(want, info.size);
100
+ const position = info.size - length;
101
+ const buf = Buffer.allocUnsafe(length);
102
+ const { bytesRead } = await handle.read(buf, 0, length, position);
103
+ const tail = buf.subarray(0, bytesRead);
104
+ if (tail.includes(0)) return false;
105
+ const cut = trailingBlankBytes(tail.toString("latin1"), position === 0);
106
+ if (cut === 0) return false;
107
+ if (cut > 0) {
108
+ await handle.truncate(info.size - cut);
109
+ return true;
110
+ }
111
+ if (position === 0 || want >= MAX_TAIL) return false;
112
+ want *= 2;
113
+ }
114
+ } catch {
115
+ return false;
116
+ } finally {
117
+ await handle?.close().catch(() => {
118
+ });
119
+ }
120
+ }
121
+ async function trimAll(files, task = trimFile) {
122
+ const changed = [];
123
+ let next = 0;
124
+ const worker = async () => {
125
+ for (let i = next++; i < files.length; i = next++) {
126
+ if (await task(files[i])) changed.push(files[i]);
127
+ }
128
+ };
129
+ await Promise.all(Array.from({ length: Math.min(CONCURRENCY, files.length) }, worker));
130
+ return changed;
131
+ }
132
+ function gitLines(args) {
133
+ try {
134
+ const out = execFileSync("git", args, { encoding: "utf8", maxBuffer: 64 * 1024 * 1024, windowsHide: true });
135
+ return out.split("\0").map((s) => s.trim()).filter(Boolean);
136
+ } catch {
137
+ return [];
138
+ }
139
+ }
140
+ function repoRoot() {
141
+ try {
142
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], windowsHide: true }).trim() || null;
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+ function stage(files) {
148
+ for (let i = 0; i < files.length; i += 500) {
149
+ try {
150
+ execFileSync("git", ["add", "--", ...files.slice(i, i + 500)], { stdio: "ignore", windowsHide: true });
151
+ } catch {
152
+ }
153
+ }
154
+ }
155
+ async function runTrimScan(all) {
156
+ const root = repoRoot();
157
+ if (root === null) return { changed: [], unstaged: [], count: 0, notRepo: true };
158
+ const listed = all ? gitLines(["ls-files", "-z"]) : gitLines(["diff", "--cached", "--name-only", "--diff-filter=ACM", "-z"]);
159
+ const ignore = readIgnoreGlobs(root).map(globToRegExp);
160
+ const files = ignore.length ? listed.filter((f) => !ignore.some((re) => re.test(f))) : listed;
161
+ if (files.length === 0) return { changed: [], unstaged: [], count: 0 };
162
+ const dirty = all ? /* @__PURE__ */ new Set() : new Set(gitLines(["diff", "--name-only", "-z"]));
163
+ const changed = await trimAll(files);
164
+ const unstaged = changed.filter((f) => dirty.has(f));
165
+ const restage = changed.filter((f) => !dirty.has(f));
166
+ if (!all && restage.length) stage(restage);
167
+ return { changed, unstaged, count: files.length };
168
+ }
169
+ async function runTrimHook(payload) {
170
+ try {
171
+ const file = JSON.parse(payload || "{}")?.tool_input?.file_path;
172
+ if (typeof file === "string" && file) await trimFile(file);
173
+ } catch {
174
+ }
175
+ return 0;
176
+ }
177
+ async function runTrimScanCli(all) {
178
+ const r = await runTrimScan(all);
179
+ if (r.notRepo) {
180
+ console.error("enigma-trim: not a git repository; nothing to do.");
181
+ return 0;
182
+ }
183
+ if (r.changed.length === 0) {
184
+ console.log(`enigma-trim: ${r.count} ${all ? "tracked" : "staged"} file(s) checked, no trailing blank lines.`);
185
+ return 0;
186
+ }
187
+ const staged = r.changed.length - r.unstaged.length;
188
+ console.log(`enigma-trim: removed a trailing blank line from ${r.changed.length} file(s)${all ? "" : `, ${staged} re-staged`}.`);
189
+ for (const f of r.changed.slice(0, 20)) console.log(` - ${f}`);
190
+ if (r.changed.length > 20) console.log(` ... and ${r.changed.length - 20} more`);
191
+ if (r.unstaged.length) {
192
+ console.log(`
193
+ enigma-trim: ${r.unstaged.length} file(s) were fixed on disk but NOT re-staged, because they also`);
194
+ console.log("hold unstaged edits and `git add` would have pulled those into this commit:");
195
+ for (const f of r.unstaged) console.log(` ! ${f}`);
196
+ }
197
+ return 0;
198
+ }
199
+ var trimEntry = process.argv[1] ?? "";
200
+ var isTrimEntry = /(^|[\\/])trim\.[mc]?[jt]s$/.test(trimEntry);
201
+ if (isTrimEntry && fileURLToPath(import.meta.url) === trimEntry) {
202
+ void runTrimScanCli(process.argv.includes("--all")).then((code) => process.exit(code));
203
+ }
204
+ export {
205
+ isSkipped,
206
+ readIgnoreGlobs,
207
+ runTrimHook,
208
+ runTrimScan,
209
+ runTrimScanCli,
210
+ trailingBlankBytes,
211
+ trimFile
212
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enigma-cli",
3
- "version": "1.32.1",
3
+ "version": "1.32.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": {
@@ -20,7 +20,8 @@
20
20
  "guardrails": "tsx src/guardrails.ts --all",
21
21
  "verify": "npm run typecheck && npm run check && npm run guard && npm run guardrails",
22
22
  "prepublishOnly": "npm run verify && npm run build",
23
- "postinstall": "node bin/postinstall.mjs"
23
+ "postinstall": "node bin/postinstall.mjs",
24
+ "trim": "tsx src/trim.ts --all"
24
25
  },
25
26
  "files": [
26
27
  "bin",