enigma-cli 1.33.5 → 1.33.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/memory/AGENTS.md +2 -0
- package/assets/memory/CLAUDE.md +2 -0
- package/assets/skills/anti-overengineering-policy/skill.json +1 -1
- package/assets/skills/anti-overengineering-review/skill.json +1 -1
- package/assets/skills/backend-policy/SKILL.md +39 -2
- package/assets/skills/backend-policy/skill.json +5 -5
- package/assets/skills/ciphera-style-policy/SKILL.md +26 -1
- package/assets/skills/ciphera-style-policy/skill.json +5 -5
- package/assets/skills/code-review-policy/SKILL.md +2 -0
- package/assets/skills/code-review-policy/skill.json +4 -4
- package/assets/skills/core-engineering-policy/SKILL.md +19 -1
- package/assets/skills/core-engineering-policy/skill.json +5 -5
- package/assets/skills/database-expert/SKILL.md +19 -1
- package/assets/skills/database-expert/skill.json +4 -4
- package/assets/skills/debugging-policy/SKILL.md +2 -1
- package/assets/skills/debugging-policy/skill.json +4 -4
- package/assets/skills/dependency-policy/skill.json +1 -1
- package/assets/skills/email-policy/skill.json +1 -1
- package/assets/skills/frontend-design/skill.json +1 -1
- package/assets/skills/frontend-policy/SKILL.md +13 -1
- package/assets/skills/frontend-policy/skill.json +4 -4
- package/assets/skills/git-policy/skill.json +1 -1
- package/assets/skills/logo-sourcing-policy/skill.json +1 -1
- package/assets/skills/security-policy/SKILL.md +1 -0
- package/assets/skills/security-policy/skill.json +4 -4
- package/assets/skills/skill-creator/assets/eval_review.html +2 -0
- package/assets/skills/skill-creator/skill.json +3 -3
- package/assets/skills/task-completion-policy/SKILL.md +1 -0
- package/assets/skills/task-completion-policy/skill.json +4 -4
- package/assets/skills/technical-writing-policy/skill.json +1 -1
- package/assets/skills/testing-policy/skill.json +1 -1
- package/assets/skills/validation-policy/skill.json +1 -1
- package/bin/checksums.json +4 -4
- package/dist/guardrails.js +281 -5
- package/package.json +1 -1
package/assets/memory/AGENTS.md
CHANGED
|
@@ -39,6 +39,7 @@ Non-negotiable, language-agnostic defaults - apply them by default without being
|
|
|
39
39
|
- Validate EVERY external input (request body, query, params, event payload, form field, CLI arg, webhook/message) against an explicit schema before use - Zod (TS/JS), Pydantic (Python), the language's equivalent elsewhere. Never consume an unvalidated shape or leave it open-ended. When the input is a tagged/event union, validate the discriminant AND that specific variant's body, with the expected fields typed.
|
|
40
40
|
- Normalize before validating, on the client AND the server, from one shared normalizer: trim every string, lowercase the email, capitalize each word of a person's name, canonicalize a link or handle to one stored form. A check that cannot fail is not validation - never patch the value into validity and then check the patched value.
|
|
41
41
|
- Frontend forms: validate in real time against the same schema, on EVERY field that has a rule and not only the ones with a famous format, and use optimistic UI with rollback on failure for user-facing mutations.
|
|
42
|
+
- Never block the first paint on data: ship the HTML shell, then request the data. Everything that does not depend on the response renders now (nav, headings, table chrome, filters, anything already cached) and only the region genuinely waiting gets a skeleton shaped like its content - never a full-page loader, and never a page that renders nothing until the fetch resolves. The rules are frontend-policy's Instant First Paint.
|
|
42
43
|
- Cache reads on the client (localStorage/sessionStorage, or the data layer's cache) with a short TTL (~30s or more) to avoid redundant queries and survive rate limits; invalidate on write.
|
|
43
44
|
- Build reusable, composable components instead of duplicating UI - e.g. a single Input that renders a show/hide toggle when the type is password. Reuse before writing new.
|
|
44
45
|
- Never use the browser's native `alert`/`confirm`/`prompt` - use a dialog/modal component that matches the page design.
|
|
@@ -48,6 +49,7 @@ Non-negotiable, language-agnostic defaults - apply them by default without being
|
|
|
48
49
|
|
|
49
50
|
- Treat every task as mission-critical: assume lives and irreversible consequences ride on this being genuinely correct, and that nobody will re-read your work before relying on it. A false report of success is therefore far worse than an honest failure. Finish every part of what was asked with nothing left pending, and before claiming it is done VERIFY it actually works - exercise the exact behavior requested (run it, test it, reproduce the scenario), not merely that it compiles or typechecks. If you have not verified it, do not say it is done; state precisely what remains or is unverified.
|
|
50
51
|
- A message that bundles several asks, questions, or items is a MULTI-PART task - even if it is just two, three, or four things. Before doing anything, extract EVERY distinct ask into an explicit list (the runtime's todo system when it has one, else a written checklist) and treat the request as unfinished until every item on that list is addressed. Never answer the first ask and drop, summarize away, or postpone the rest. When you present a plan, execute the whole plan - do not stop after listing it.
|
|
52
|
+
- A concrete case the user names is an EXAMPLE OF A CLASS, not the whole job ("this label overflows", "this endpoint is unvalidated"). Unless the user scoped it there, state the general rule, sweep deterministically for every other site it applies to, fix them all in this same change, and encode the rule in exactly one tier. Deliberately restated here so it holds even when a skill does not load; the procedure is core-engineering-policy's Generalization Rule.
|
|
51
53
|
- 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.
|
|
52
54
|
- 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.
|
|
53
55
|
- "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.
|
package/assets/memory/CLAUDE.md
CHANGED
|
@@ -39,6 +39,7 @@ Non-negotiable, language-agnostic defaults - apply them by default without being
|
|
|
39
39
|
- Validate EVERY external input (request body, query, params, event payload, form field, CLI arg, webhook/message) against an explicit schema before use - Zod (TS/JS), Pydantic (Python), the language's equivalent elsewhere. Never consume an unvalidated shape or leave it open-ended. When the input is a tagged/event union, validate the discriminant AND that specific variant's body, with the expected fields typed.
|
|
40
40
|
- Normalize before validating, on the client AND the server, from one shared normalizer: trim every string, lowercase the email, capitalize each word of a person's name, canonicalize a link or handle to one stored form. A check that cannot fail is not validation - never patch the value into validity and then check the patched value.
|
|
41
41
|
- Frontend forms: validate in real time against the same schema, on EVERY field that has a rule and not only the ones with a famous format, and use optimistic UI with rollback on failure for user-facing mutations.
|
|
42
|
+
- Never block the first paint on data: ship the HTML shell, then request the data. Everything that does not depend on the response renders now (nav, headings, table chrome, filters, anything already cached) and only the region genuinely waiting gets a skeleton shaped like its content - never a full-page loader, and never a page that renders nothing until the fetch resolves. The rules are frontend-policy's Instant First Paint.
|
|
42
43
|
- Cache reads on the client (localStorage/sessionStorage, or the data layer's cache) with a short TTL (~30s or more) to avoid redundant queries and survive rate limits; invalidate on write.
|
|
43
44
|
- Build reusable, composable components instead of duplicating UI - e.g. a single Input that renders a show/hide toggle when the type is password. Reuse before writing new.
|
|
44
45
|
- Never use the browser's native `alert`/`confirm`/`prompt` - use a dialog/modal component that matches the page design.
|
|
@@ -48,6 +49,7 @@ Non-negotiable, language-agnostic defaults - apply them by default without being
|
|
|
48
49
|
|
|
49
50
|
- Treat every task as mission-critical: assume lives and irreversible consequences ride on this being genuinely correct, and that nobody will re-read your work before relying on it. A false report of success is therefore far worse than an honest failure. Finish every part of what was asked with nothing left pending, and before claiming it is done VERIFY it actually works - exercise the exact behavior requested (run it, test it, reproduce the scenario), not merely that it compiles or typechecks. If you have not verified it, do not say it is done; state precisely what remains or is unverified.
|
|
50
51
|
- A message that bundles several asks, questions, or items is a MULTI-PART task - even if it is just two, three, or four things. Before doing anything, extract EVERY distinct ask into an explicit list (the runtime's todo system when it has one, else a written checklist) and treat the request as unfinished until every item on that list is addressed. Never answer the first ask and drop, summarize away, or postpone the rest. When you present a plan, execute the whole plan - do not stop after listing it.
|
|
52
|
+
- A concrete case the user names is an EXAMPLE OF A CLASS, not the whole job ("this label overflows", "this endpoint is unvalidated"). Unless the user scoped it there, state the general rule, sweep deterministically for every other site it applies to, fix them all in this same change, and encode the rule in exactly one tier. Deliberately restated here so it holds even when a skill does not load; the procedure is core-engineering-policy's Generalization Rule.
|
|
51
53
|
- 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.
|
|
52
54
|
- 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.
|
|
53
55
|
- "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.
|
|
@@ -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.
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
8
8
|
"sha": "f742a2be3f328b9ea1ff9a35a449177c2cbec35ad16e46f7054b7a873a2ab017"
|
|
9
9
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: backend-policy
|
|
3
|
-
description: Backend/API architecture - controller-service-repository layering, request/response handling, API and request optimization (batching, avoiding redundant calls), server-side caching (Redis) with invalidation, and Zod boundary validation. Use when designing or changing API endpoints, services, controllers, server business logic, or backend request flow.
|
|
3
|
+
description: Backend/API architecture - controller-service-repository layering, modern TypeScript project configuration (target/module/moduleResolution, strict flags, and the `@/*` path alias), request/response handling, API and request optimization (batching, avoiding redundant calls), server-side caching (Redis) with invalidation, and Zod boundary validation. Use when designing or changing API endpoints, services, controllers, server business logic, or backend request flow, and when scaffolding or fixing a backend's tsconfig.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Backend & API Architecture Policy
|
|
@@ -8,7 +8,44 @@ description: Backend/API architecture - controller-service-repository layering,
|
|
|
8
8
|
## Activation Scope
|
|
9
9
|
|
|
10
10
|
- Apply whenever the task involves API endpoints, server business logic, services, controllers, or backend request flow.
|
|
11
|
-
- Owns server-side layering, API/request optimization, and server-side caching. Strict input validation rules live in validation-policy; persistence and query rules live in database-expert.
|
|
11
|
+
- Owns server-side layering, the TypeScript project configuration a backend is built on, API/request optimization, and server-side caching. Strict input validation rules live in validation-policy; persistence and query rules live in database-expert; import style lives in ciphera-style-policy.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## TypeScript Project Configuration
|
|
16
|
+
|
|
17
|
+
The tsconfig is set once, at scaffold time, and every import in the project inherits the consequences. Get it wrong and the cost shows up as noise in thousands of specifiers.
|
|
18
|
+
|
|
19
|
+
- Pick the emit story first, because it decides everything else:
|
|
20
|
+
- **Bundled or run from source** - tsup/esbuild/Vite/Next, or executed by tsx or Bun. This is the default for a service or a CLI. Use `"module": "esnext"` with `"moduleResolution": "bundler"`, and specifiers carry no file extension.
|
|
21
|
+
- **Emitted by `tsc` for Node's own ESM loader** - a published library that ships plain `.js` and has no build step beyond `tsc`. Use `"module": "nodenext"`, and then every relative specifier MUST end in `.js` (Node's loader does no extension guessing). That is the price of the choice; do not pay it by accident on a service that is bundled anyway.
|
|
22
|
+
- Never `"moduleResolution": "node"` (or `"node10"`). It is the pre-2022 resolver and it ignores a package's `exports` map, so a modern dependency resolves to the wrong entry point or fails outright. Never a `"target"` below `es2022` either - it downlevels syntax every runtime you support has shipped for years.
|
|
23
|
+
- Baseline for a new backend:
|
|
24
|
+
|
|
25
|
+
```jsonc
|
|
26
|
+
{
|
|
27
|
+
"compilerOptions": {
|
|
28
|
+
"target": "es2022",
|
|
29
|
+
"lib": ["es2023"],
|
|
30
|
+
"module": "esnext",
|
|
31
|
+
"moduleResolution": "bundler",
|
|
32
|
+
"strict": true,
|
|
33
|
+
"noUncheckedIndexedAccess": true, // arr[i] is T | undefined, which is the truth
|
|
34
|
+
"verbatimModuleSyntax": true, // type imports erase predictably, no surprise runtime import
|
|
35
|
+
"esModuleInterop": true,
|
|
36
|
+
"skipLibCheck": true,
|
|
37
|
+
"forceConsistentCasingInFileNames": true,
|
|
38
|
+
"noEmit": true, // the bundler emits; tsc only typechecks
|
|
39
|
+
"baseUrl": ".",
|
|
40
|
+
"paths": { "@/*": ["./src/*"] }
|
|
41
|
+
},
|
|
42
|
+
"include": ["src"]
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- The `paths` alias is part of the baseline, not an optional extra: services, repositories and schemas are imported across the whole tree, and `../../../lib/db` is the shape that makes moving a module a repo-wide edit. Import through `@/` and specifier stability comes for free (ciphera-style-policy owns the import-style rules).
|
|
47
|
+
- Declare the alias in every consumer that resolves modules itself, or it only works in the editor: the bundler config where it does not read tsconfig, `moduleNameMapper` for Jest, `vite-tsconfig-paths` for Vitest. Bun and tsx read tsconfig directly and need nothing.
|
|
48
|
+
- Typechecking is a gate, not an editor feature: `tsc --noEmit` runs in the same command as the tests.
|
|
12
49
|
|
|
13
50
|
---
|
|
14
51
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backend-policy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
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-08-
|
|
7
|
-
"cliVersion": "1.33.
|
|
8
|
-
"sha": "
|
|
5
|
+
"description": "Backend/API architecture: controller-service-repository layering, modern TypeScript project configuration (module resolution, strict flags, @/* path alias), API and request optimization (batching, avoiding redundant calls, skipping no-op writes), server-side caching (Redis), and Zod boundary validation.",
|
|
6
|
+
"updated": "2026-08-02T19:59:01+02:00",
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
8
|
+
"sha": "652637f818b4ce4f4d5d83fa68fa57fe7842e32fb179523958c5496af54bb938"
|
|
9
9
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ciphera-style-policy
|
|
3
|
-
description: Ciphera code style conventions - mandatory formatting and language idioms for source code (TypeScript-first, applies to every language) - American-English naming, double quotes, string interpolation, length-sorted imports, one statement per module and a namespace import (`import * as ns`) instead of a long named list from a project module, 4-space indentation, comment/JSDoc format, compact single-line blocks, and code-level anti-patterns (barrel files, external CDN/hosting dependencies). Use whenever writing, refactoring, or reviewing source code.
|
|
3
|
+
description: Ciphera code style conventions - mandatory formatting and language idioms for source code (TypeScript-first, applies to every language) - American-English naming, double quotes, string interpolation, length-sorted imports, one statement per module and a namespace import (`import * as ns`) instead of a long named list from a project module, path-alias specifiers (`@/x`) instead of deep relative chains and no file extension in an import, 4-space indentation, comment/JSDoc format, compact single-line blocks, and code-level anti-patterns (barrel files, external CDN/hosting dependencies). Use whenever writing, refactoring, or reviewing source code.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Ciphera Code Style Policy
|
|
@@ -79,6 +79,31 @@ if (!conf.OUTPUT_STYLES.includes(style)) return;
|
|
|
79
79
|
- Do not import from external hostings or CDNs; depend on a package name, not a remote URL.
|
|
80
80
|
- For obscure libraries, vendor the needed code into the project utilities instead of adding a fragile dependency.
|
|
81
81
|
|
|
82
|
+
### Specifiers: alias over directory counting
|
|
83
|
+
|
|
84
|
+
- A TypeScript project declares a path alias and imports through it. In `tsconfig.json`: `"baseUrl": "."` plus `"paths": { "@/*": ["./src/*"] }`. Set this up when you scaffold the project, not once the chains get long.
|
|
85
|
+
- Use the alias for anything outside the importing file's own neighbourhood: `@/services/user`, `@/lib/db`, `@/components/Input`.
|
|
86
|
+
- Keep a relative specifier for a file in the same folder (`./helpers`) or its parent (`../types`). Those say "this belongs with me", which is information; `../../../` says only where the file happens to sit today.
|
|
87
|
+
- A relative chain encodes the position of BOTH files. Move either one and specifiers that had nothing to do with the change have to be rewritten, so a refactor that should be a rename becomes a diff across the tree. An alias is stable under both moves and reads as an absolute address.
|
|
88
|
+
- The alias is a resolver convention, not a runtime one: bundlers (Vite, webpack, esbuild, tsup, Next), tsx and Bun all read it from tsconfig with no extra setup. Jest needs the same map in `moduleNameMapper`; if the test runner has not been told about it, keep the test's import relative rather than leaving it broken.
|
|
89
|
+
- One alias prefix per project. Adding `~/`, `#/` and `@app/` beside `@/` just moves the counting problem into deciding which prefix to write.
|
|
90
|
+
|
|
91
|
+
### No file extensions in specifiers
|
|
92
|
+
|
|
93
|
+
- Write `@/services/user` and `./helpers`, never `./helpers.ts`, `./helpers.js`, or `./helpers.tsx`. The resolver finds the source file; the extension only pins the import to a build artifact.
|
|
94
|
+
- `.js` on a TypeScript file names something that does not exist in the source tree, so the specifier stops matching the file it points at and a reader has to translate it back. `.ts` needs `allowImportingTsExtensions` and breaks the day the project emits.
|
|
95
|
+
- The one case where extensions are mandatory is a project emitted by `tsc` for Node's own ESM loader (`"module": "nodenext"`). That is a tsconfig decision, taken once for the project - see backend-policy for which side to be on - not a choice made per import. When the project is built by a bundler or run by tsx/Bun, `"moduleResolution": "bundler"` is the setting and specifiers carry no extension.
|
|
96
|
+
- A non-module asset keeps its real extension, because that IS its name: `import styles from "./table.css"`, `import icon from "./logo.svg"`.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
// Bad: counts directories, and names a .js file that does not exist in src/.
|
|
100
|
+
import { createUser } from "../../../services/user.js";
|
|
101
|
+
|
|
102
|
+
// Good
|
|
103
|
+
import { createUser } from "@/services/user";
|
|
104
|
+
import { formatRow } from "./helpers";
|
|
105
|
+
```
|
|
106
|
+
|
|
82
107
|
```ts
|
|
83
108
|
// Good
|
|
84
109
|
import axios from "axios";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ciphera-style-policy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
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-
|
|
7
|
-
"cliVersion": "1.33.
|
|
8
|
-
"sha": "
|
|
5
|
+
"description": "Ciphera code style conventions (formatting, naming, imports incl. namespace imports for wide module surfaces, path-alias specifiers and no file extensions, comments, code-level anti-patterns; TypeScript-first, language-agnostic).",
|
|
6
|
+
"updated": "2026-08-02T19:59:01+02:00",
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
8
|
+
"sha": "3beb0383a9cb0331ef8a579308a2517d24d57a8ba33751ad17d16d0a4f66e344"
|
|
9
9
|
}
|
|
@@ -30,6 +30,7 @@ Before declaring a change complete, verify:
|
|
|
30
30
|
4. No secrets, credentials, or sensitive data are included.
|
|
31
31
|
5. Existing patterns, naming, and structure are followed (per core-engineering-policy).
|
|
32
32
|
6. Tests exist and pass for the changed behavior (per testing-policy).
|
|
33
|
+
7. The change covers the whole class the request implied, not only the example the user named (per core-engineering-policy's Generalization Rule) - or states which siblings were deliberately left and why.
|
|
33
34
|
|
|
34
35
|
---
|
|
35
36
|
|
|
@@ -65,4 +66,5 @@ A change should not be delivered if it:
|
|
|
65
66
|
- Breaks or skips tests, or ships untested critical behavior.
|
|
66
67
|
- Duplicates logic that already exists, or stores duplicated/derivable data without justification.
|
|
67
68
|
- Mixes unrelated concerns in one change.
|
|
69
|
+
- Fixes the reported instance while identical instances of the same defect stay untouched in the codebase.
|
|
68
70
|
- Leaves the codebase less consistent than it found it.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-review-policy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Pre-delivery self-review gate, prioritized review dimensions, and change-quality criteria.",
|
|
6
|
-
"updated": "2026-
|
|
7
|
-
"cliVersion": "1.33.
|
|
8
|
-
"sha": "
|
|
6
|
+
"updated": "2026-08-02T04:15:47+02:00",
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
8
|
+
"sha": "b35f0bb4a29f2346d9a1b00a4bfee9557a9b6a44116d9e6e59abcd945466dbcb"
|
|
9
9
|
}
|
|
@@ -22,6 +22,24 @@ description: Highest-authority engineering rules - priority hierarchy, modular a
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
## Generalization Rule (Example -> Class)
|
|
26
|
+
|
|
27
|
+
- A report, complaint, or request that names a concrete case is an EXAMPLE of a class, not the boundary of the task. "This label overflows", "this endpoint is unvalidated", "this action should be an icon", "this table has no empty state" all describe an invariant that is being violated in more than one place. Fixing only the named instance leaves its siblings broken and guarantees the same report returns, one site at a time.
|
|
28
|
+
- Procedure, in this order:
|
|
29
|
+
1. Name the rule. Restate the example as a general invariant ("every X must Y") at the widest scope where it stays true. If it genuinely cannot be stated generally, the request really was a one-off - say so and move on.
|
|
30
|
+
2. Sweep. Enumerate every place the rule applies with deterministic commands (grep, AST or type search, route/component listings), never by recalling or sampling. The result is an inventory: when it spans many sites, run it through task-completion-policy's ledger instead of fixing whatever the first search happened to surface.
|
|
31
|
+
3. Fix the whole class in this same change. Verify each site, not only the one that was reported.
|
|
32
|
+
4. Encode the rule so it holds without you, routed by tier:
|
|
33
|
+
- Mechanically checkable from a file-local signature -> a lint, guardrail, or CI rule. Deterministic and costs no context.
|
|
34
|
+
- Semantic but domain-scoped -> the owning policy skill, so it loads only when that domain is in scope.
|
|
35
|
+
- Semantic and universal -> the project's own always-on memory file (its root CLAUDE.md / AGENTS.md). When the rule belongs in the enigma-managed global kernel instead, change it through enigma's own memory channel (the dashboard memory editor, which records the edit) - never hand-edit the deployed file. An unrecorded edit makes that file no longer enigma-written, and sync skips it from then on, silently opting the user out of every future kernel update.
|
|
36
|
+
- Never encode one rule in two tiers; the most deterministic tier that can express it wins.
|
|
37
|
+
5. Report the rule inferred, the sites fixed, and where the rule was encoded.
|
|
38
|
+
- Stay on the single instance only when the user scoped it there ("only here", "just this one"), or when generalizing would require a destructive action or a decision that is genuinely the user's. Then say explicitly what was left unfixed and why - silently narrowing the scope is the failure this rule exists to prevent.
|
|
39
|
+
- Bug sweeps search for the root cause, not the symptom (debugging-policy). The pre-delivery check that no sibling was left behind is in code-review-policy.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
25
43
|
## Skill Activation Discipline (Use the Harness)
|
|
26
44
|
|
|
27
45
|
- This is a modular harness. Each domain has a dedicated skill; the agent MUST apply the matching skill whenever its domain is in scope, not just this core policy.
|
|
@@ -79,7 +97,7 @@ If rules conflict, apply this priority order:
|
|
|
79
97
|
|
|
80
98
|
This core policy owns orchestration, architecture, and the global rules. Each concern below is owned by its own skill:
|
|
81
99
|
|
|
82
|
-
- core-engineering-policy: highest-authority orchestration, priority hierarchy, language, output, modular architecture, reuse, security baseline, documentation. (this skill)
|
|
100
|
+
- core-engineering-policy: highest-authority orchestration, priority hierarchy, the generalization rule (a named example is a class), language, output, modular architecture, reuse, security baseline, documentation. (this skill)
|
|
83
101
|
- database-expert: schema design, normalization/anti-duplication, query and index optimization, scalability, RGPD/GDPR encryption, migrations.
|
|
84
102
|
- validation-policy: strict frontend + backend schema validation (Zod), schema consistency, client-facing error handling.
|
|
85
103
|
- frontend-policy: frontend structure, reusable components, abstraction threshold, client-side caching, optimistic UI and rollback.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "core-engineering-policy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
|
-
"description": "Core engineering execution policy and harness orchestration (highest-authority rules).",
|
|
6
|
-
"updated": "2026-
|
|
7
|
-
"cliVersion": "1.33.
|
|
8
|
-
"sha": "
|
|
5
|
+
"description": "Core engineering execution policy and harness orchestration (highest-authority rules), including the generalization rule that treats a named example as a class to sweep and fix.",
|
|
6
|
+
"updated": "2026-08-02T04:28:51+02:00",
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
8
|
+
"sha": "95091678d4e72e3a9b1ef3ad0514e503a2a631f0d134bf772b4a1b3c66881995"
|
|
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), 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.
|
|
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, query cost and latency discipline (bounded reads, filtering and paginating in the database rather than in application code, round-trip count, expensive COUNT(*) totals, precomputed aggregates, statement timeouts and pooling, EXPLAIN on realistic data), 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)
|
|
@@ -135,6 +135,21 @@ Any denormalized or duplicated value MUST have:
|
|
|
135
135
|
- Validate every non-trivial query with EXPLAIN / EXPLAIN ANALYZE and confirm index usage before shipping.
|
|
136
136
|
- Use parameterized/prepared statements exclusively - never build SQL by string concatenation.
|
|
137
137
|
|
|
138
|
+
### Cost, Latency & Round Trips (Know What The Query Costs)
|
|
139
|
+
|
|
140
|
+
Every query has a price - rows examined, IO, CPU, connection time, and on a managed database an actual bill. Write it knowing the number, not hoping it is small. "It was fast locally" is not a measurement: a seq scan over 200 development rows and over 20 million production rows look identical from the app.
|
|
141
|
+
|
|
142
|
+
- **Every query is bounded.** A read that can return an unbounded set carries an explicit `LIMIT` (and the pagination that goes with it). A list endpoint with no cap is an outage waiting for the row count to grow.
|
|
143
|
+
- **Filter, sort, aggregate and paginate in the DATABASE.** Fetching a table to slice, sort, count or sum it in application code moves the whole result over the wire and throws most of it away - and no index can help once the rows have left the engine.
|
|
144
|
+
- **Count the round trips, not just the queries.** A loop issuing one query per item pays the network latency every iteration: replace it with one set-based statement (a single `IN`, a join, or the ORM's `include`/`select` for the relation). Two round trips at 30 ms are cheaper than twenty perfect queries.
|
|
145
|
+
- **Ask only for what you use.** Column lists over `SELECT *`, and on an ORM an explicit `select` - it is also what makes an index-only scan possible, and it keeps a `TEXT`/`JSONB` column you never read out of every row you fetch.
|
|
146
|
+
- **Total counts are expensive.** `COUNT(*)` with the same filters as the page is a second full pass over the matched rows. Prefer "load more"/keyset paging with no total, an approximate count (`reltuples`, a cheap estimator) for a scale hint, or a maintained counter when the exact number is genuinely part of the product.
|
|
147
|
+
- **Aggregations over large tables are precomputed, not recomputed per request.** A rollup table, a materialized view refreshed on a schedule, or an incrementally maintained counter - a dashboard that aggregates the whole history on every load will not survive its own success.
|
|
148
|
+
- **Give every statement a timeout** (`statement_timeout` per role or per transaction) so one pathological query cannot pin a connection and cascade into pool exhaustion. Pair it with a pool sized to the database's real connection ceiling, through a pooler in serverless (a function per request otherwise opens a connection per request).
|
|
149
|
+
- **Never hold a transaction open across an external call.** An HTTP request or a queue publish inside a transaction holds its locks for the remote service's latency, including its timeouts.
|
|
150
|
+
- **Measure against realistic volume before shipping.** `EXPLAIN ANALYZE` on production-like data, and read the two numbers that matter: rows examined versus rows returned. A large ratio means the index does not match the predicate you actually wrote, whatever the plan calls itself.
|
|
151
|
+
- **Set a budget for the hot path and check it.** Name the target (a simple read in single-digit milliseconds; a page load's queries in tens, not hundreds), keep the query count per request visible in logs or traces, and treat a regression as a defect. Caching an expensive read is the last step, not the fix for a query that was never designed (backend-policy owns the cache layer and its invalidation).
|
|
152
|
+
|
|
138
153
|
---
|
|
139
154
|
|
|
140
155
|
## Scalability (Design for Large Scale by Default)
|
|
@@ -233,6 +248,9 @@ Any denormalized or duplicated value MUST have:
|
|
|
233
248
|
## Anti-Patterns (Never Do)
|
|
234
249
|
|
|
235
250
|
- Using auto-increment / serial / IDENTITY integer primary keys, or exposing sequential numeric IDs instead of UUIDs.
|
|
251
|
+
- Shipping a query whose cost was never measured on realistic data, or an unbounded read with no `LIMIT`.
|
|
252
|
+
- Fetching rows to filter, sort, count or paginate them in application code.
|
|
253
|
+
- Querying inside a loop when one set-based statement would do.
|
|
236
254
|
- Duplicating data without a documented sync strategy and justification.
|
|
237
255
|
- Storing easily computable values that should be derived at read time.
|
|
238
256
|
- SELECT * on hot paths or fetching columns that are not used.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "database-expert",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Senior database architecture policy: engine selection (PostgreSQL by default, SQLite only for local-first/embedded stores), ORM selection (Prisma in TypeScript/JavaScript), query optimization, anti-duplication/normalization, scalability, and RGPD/GDPR encryption.",
|
|
6
|
-
"updated": "2026-08-
|
|
7
|
-
"cliVersion": "1.33.
|
|
8
|
-
"sha": "
|
|
6
|
+
"updated": "2026-08-02T19:59:01+02:00",
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
8
|
+
"sha": "f4ccf51286027c97c8402f3bac6c894e05a84cba2de014463cb3a83be17537d4"
|
|
9
9
|
}
|
|
@@ -29,7 +29,7 @@ description: Reproduce-isolate-fix debugging methodology with root-cause discipl
|
|
|
29
29
|
5. Confirm: prove the hypothesis (the failing case maps to the identified cause) before fixing.
|
|
30
30
|
6. Fix: address the underlying cause at the right layer.
|
|
31
31
|
7. Verify: add a regression test that fails before the fix and passes after (per testing-policy), then run the relevant suite.
|
|
32
|
-
8.
|
|
32
|
+
8. Generalize: the reported failure is one instance of a class. Search deterministically for every other occurrence of the same root cause, fix them in this same change, and encode the invariant where it can be checked without you (core-engineering-policy's Generalization Rule).
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
@@ -49,6 +49,7 @@ description: Reproduce-isolate-fix debugging methodology with root-cause discipl
|
|
|
49
49
|
- Distinguish the trigger (what surfaced it) from the cause (what is actually wrong).
|
|
50
50
|
- A workaround is acceptable only as an explicit, temporary measure with the real cause documented.
|
|
51
51
|
- When the cause spans multiple components, fix it where the invariant is actually owned.
|
|
52
|
+
- The reported symptom is a sample, not the population. Once the cause is known, find its other victims by searching for the CAUSE (the wrong call, the missing guard, the unchecked shape), not for the symptom the user happened to notice.
|
|
52
53
|
|
|
53
54
|
---
|
|
54
55
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "debugging-policy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Reproduce-isolate-fix debugging methodology with root-cause discipline and regression verification.",
|
|
6
|
-
"updated": "2026-
|
|
7
|
-
"cliVersion": "1.33.
|
|
8
|
-
"sha": "
|
|
6
|
+
"updated": "2026-08-02T04:15:47+02:00",
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
8
|
+
"sha": "968876bcac9ce6e5a20be05c89ac97191b112c9e08679a8dc506d150c375fec4"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Dependency and supply-chain security: lockfiles and reproducible installs, version pinning, vulnerability auditing, vetting/minimizing packages, vendoring, and SBOM/provenance.",
|
|
6
6
|
"updated": "2026-06-01T00:45:28+02:00",
|
|
7
|
-
"cliVersion": "1.33.
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
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.
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
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.
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
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 as the preferred default with pagination as the deliberate exception when the design or the user calls for it, 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, data views that ship their own affordances by default (a log, expense, transaction or history table is not done when the rows render - it needs the search, the filters its column kinds imply, a date range, sort, filter state kept in the URL, and an export of the filtered set), every reference to an entity being a way into it (a name, id, project or path in a row links to that record, reveals it in a hover card, or at minimum copies and filters by it - never inert text, with machine codes given human labels and raw payloads never dumped into a cell), 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, a log/activity/expenses/transactions/history table, 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 as the preferred default with pagination as the deliberate exception when the design or the user calls for it, 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, data views that ship their own affordances by default (a log, expense, transaction or history table is not done when the rows render - it needs the search, the filters its column kinds imply, a date range, sort, filter state kept in the URL, and an export of the filtered set), every reference to an entity being a way into it (a name, id, project or path in a row links to that record, reveals it in a hover card, or at minimum copies and filters by it - never inert text, with machine codes given human labels and raw payloads never dumped into a cell), responsive/adaptive layout (fluid units, breakpoints, no overlap or horizontal overflow, viewport meta, touch targets, and items with a fixed intrinsic size - icons, avatars, badges - pinned with flex-shrink so long text squashes the text and never the glyph), 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, and a fixed-length 2FA or emailed code that verifies itself when the last digit lands, once per distinct value and never re-firing into the attempt cap), 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, a log/activity/expenses/transactions/history table, 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
|
|
@@ -200,6 +200,8 @@ Every string is variable-length; the value on screen during development is one s
|
|
|
200
200
|
- Content whose width changes as it updates (counters, timers, prices) reflows its row on every tick. Use tabular numerals (`font-variant-numeric: tabular-nums`) or reserve the space.
|
|
201
201
|
- Absolutely positioned or overlaid text is where collisions happen, because it is outside normal flow and cannot push anything away. Constrain it with a `max-width` and check it at the narrowest breakpoint.
|
|
202
202
|
- Do not "fix" an overflow by clipping the parent. `overflow: hidden` on the container hides the symptom, and clips focus rings, tooltips and menus with it. Fix the sizing that caused it.
|
|
203
|
+
- **The other half of the same rule: some flex items must NOT shrink.** `flex-shrink: 1` is the default, so in a row of icon plus text the browser takes width from BOTH when the text runs long - and the icon, having no content to reflow, is simply squashed. The result is the 14px chevron or external-link glyph rendered 4px wide next to a long product name, which nobody notices while the sample text is short. Anything with a fixed intrinsic size - icon, avatar, badge, status dot, spinner, checkbox, the action button at the end of a row - takes `flex-shrink: 0` (Tailwind `shrink-0`, or `flex: none`). The TEXT is the element that gives up width, and it truncates or wraps as decided above.
|
|
204
|
+
- An explicit `width`/`height` on the icon does not protect it: those set the base size, not the minimum, and flex shrinks below it. An `<svg>` scales with its viewBox rather than clipping, which is exactly why it deforms silently instead of overflowing visibly. Set the guard where the icons are defined - one `svg { flex-shrink: 0 }` in the base stylesheet, or `shrink-0` inside the shared Icon component - rather than remembering it per row.
|
|
203
205
|
- Verify with worst-case content before calling it done: render the longest value you expect and confirm nothing spills out of its box or over a neighbour. The mechanical check is `element.scrollWidth <= element.clientWidth` for the box, and comparing bounding rectangles against the container for the collision - cheaper and more reliable than eyeballing it at one window size.
|
|
204
206
|
|
|
205
207
|
---
|
|
@@ -300,6 +302,13 @@ Auth is the first screen a user meets and the one most often shipped half-built.
|
|
|
300
302
|
- 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.
|
|
301
303
|
- 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".
|
|
302
304
|
- 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.
|
|
305
|
+
- **When the code has a known fixed length, the form submits itself the moment the last character lands.** A 6-digit 2FA or emailed code is complete the instant the sixth digit arrives - typed, pasted, or filled by the OS from an SMS or the authenticator - and asking for a click after that is a step the UI can take on the user's behalf. Take the length from the ONE constant the generator uses, not from a `6` hardcoded in the component, so a change to 8 does not silently break the trigger.
|
|
306
|
+
- Auto-submit needs three guards, and without them it burns the user's attempt budget:
|
|
307
|
+
- **Once per distinct complete value.** Remember the value already sent and submit only when the current one is complete AND different. A re-render, a blur, a paste that lands as two events, or an autofill that rewrites the field must not each fire a request.
|
|
308
|
+
- **Do not re-fire after a failure until the user edits the code.** A wrong code that resubmits on every keystroke can exhaust a five-attempt cap before the user finishes correcting it. Mark the field invalid, keep what they typed, and wait for a change.
|
|
309
|
+
- **Never auto-retry a `429` or a network error.** Show the wait from `Retry-After` with a countdown, and let the user trigger the next attempt (server-side limits are security-policy's).
|
|
310
|
+
- Keep the submit button, disabled while the code is incomplete. It is the affordance for anyone who does not see the field complete itself, the retry control after a failure, and the fallback when autofill misbehaves. Announce the transition in an `aria-live="polite"` region ("Verifying code...", then the result), because a form that submits with no click gives a screen-reader user nothing to go on.
|
|
311
|
+
- Do not auto-submit when the length is not fixed: a backup or recovery code of variable length, or one the user may paste with separators, has no reliable "complete" moment - normalize the value (strip spaces and dashes) and let the button be the trigger.
|
|
303
312
|
- Never keep a password, token, or code in `localStorage`, a query string, or an analytics payload. A reset token in the URL stays out of logs and out of any third-party script on the page.
|
|
304
313
|
|
|
305
314
|
---
|
|
@@ -376,6 +385,9 @@ Render the page shell immediately; never block the first paint on data. A view t
|
|
|
376
385
|
|
|
377
386
|
- Load data asynchronously via the API AFTER the shell renders (client fetch on mount, or streaming/Suspense on server components) - do not gate the component's first render on the awaited data. The layout is static and free to render now; only the contents wait.
|
|
378
387
|
- Show skeleton placeholders shaped like the real content in every region still loading (cards, rows, charts, stat tiles), not one full-page spinner. Reserve the final dimensions so nothing shifts when data lands (no layout shift / CLS).
|
|
388
|
+
- **The skeleton covers the data that is missing, and nothing else.** Most of a screen does not depend on the response and must render as itself immediately: the nav, the page title, section headings, column headers, tab bars, filter and search controls, buttons, the card frames, and any value already in hand - a name from the route, a count from the cache, anything the parent already loaded. If the only thing waiting is the table rows, only the rows are skeletons and the rest of the table is real. Blanking a region you could have rendered is the same defect as blanking the page, just smaller.
|
|
389
|
+
- The test is per element, not per screen: "does this need the response to be drawn?" If no, draw it now. A loader is what you show when there is genuinely nothing to show yet, which is rarer than it looks.
|
|
390
|
+
- Regions do not wait for each other. Independent widgets each own their request and resolve on their own, so one slow endpoint never holds back the four that already answered.
|
|
379
391
|
- This applies to ANY data-driven view - dashboards, panels, detail pages, settings screens - not only long lists. A dashboard of independent widgets renders its grid instantly and lets each widget resolve on its own (see Progressive / parallel rendering below).
|
|
380
392
|
- For an instant first paint with REAL content, read the client cache first (per Client-Side Caching) and render it immediately, then revalidate in the background (stale-while-revalidate); fall back to skeletons only on a cold cache.
|
|
381
393
|
- Keep empty and error states per region, so a single failed or slow widget shows its own inline state without blanking the whole page.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frontend-policy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.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 is the preferred default, pagination the deliberate exception when the design or the user calls for it, virtualization, skeletons, progressive loading), data views that ship their own affordances by default (a log, expense, transaction or history table is not done when the rows render: fuse.js search, the filters its column kinds imply, a date range, sort, filter state in the URL, and an export of the filtered set), every reference to an entity being a way into it (a name, id, project or path in a row links to that record, reveals it in a hover card, or at minimum copies and filters by it, never inert text; machine codes get human labels and raw payloads are never dumped into a cell), optimistic UI with rollback, visual restraint (one card level, spacing before borders, one elevation scale), icon actions (repeated row/card actions are icon-only buttons with aria-label plus title, not text labels), responsive/adaptive layout (fluid units, breakpoints, no overlap/overflow, viewport meta, touch targets), form fields that declare their keyboard and casing (autocapitalize/autocomplete/inputmode per field kind, set once in the shared Input, normalized on blur), variable-length text (min-width:0 in flex/grid, wrap vs truncate, long unbroken strings, worst-case content checks), auth screens (breached-password feedback, strength meter, cookie consent before login/register), and AI chat/agent interfaces via Vercel's AI Elements registry instead of hand-rolled message threads.",
|
|
6
|
-
"updated": "2026-08-
|
|
7
|
-
"cliVersion": "1.33.
|
|
8
|
-
"sha": "
|
|
6
|
+
"updated": "2026-08-02T19:59:01+02:00",
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
8
|
+
"sha": "6c04c2bf01f5d46d75f032fa93f5ec92244f327fb6f266de46ed448ce50a0a99"
|
|
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.
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
8
8
|
"sha": "e6dfbc33884000d9d25841bd9c5a84d6558ffd374882cb7b34451eb2cebc2161"
|
|
9
9
|
}
|
|
@@ -83,6 +83,7 @@ The same screens that check the breach corpus reject a password built out of the
|
|
|
83
83
|
- Count FAILURES, not requests, so a person typing their password wrong twice is not treated like an attack while a scripted run is stopped early.
|
|
84
84
|
- Back off exponentially and answer `429` with `Retry-After`. Keep every counter server-side; a client-held attempt count is decoration.
|
|
85
85
|
- Cap second factors hard: a handful of attempts per code, then invalidate the code and require a new one. OTP codes are single-use with a short TTL, and backup codes are single-use and stored hashed.
|
|
86
|
+
- That cap is what a self-submitting code field spends. A UI that verifies as soon as the sixth digit lands (frontend-policy's rule, and the right default) turns each correction into a real attempt, so the client submits once per distinct complete value and stops after a failure until the user edits it. The server still assumes it will not: the cap, the single-use rule and the TTL are enforced there, and an identical value replayed against an already-failed code is answered like any other attempt without extending its life.
|
|
86
87
|
- After a threshold of failures, lock the account temporarily and tell the owner by email. An unbounded lock is a denial-of-service someone else can trigger, so prefer a timed lock with a clear unlock path.
|
|
87
88
|
- Derive the client IP from the trusted proxy chain, never from a raw client-supplied header. `X-Forwarded-For` is attacker-controlled unless your edge rewrites it.
|
|
88
89
|
- The limiter must not become an oracle: an unknown account and a known one get the same response shape, status, and timing. Always run the password hash, even when the user does not exist, so the timing does not answer the question the error message refused to.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "security-policy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Application and AI-agent security: secrets, authn/authz (least privilege), credential flows with breach-checked passwords (Have I Been Pwned), OWASP Top 10, transport/crypto baseline, cookies and consent, secure logging, and agent/MCP/tool-use safety.",
|
|
6
|
-
"updated": "2026-08-
|
|
7
|
-
"cliVersion": "1.33.
|
|
8
|
-
"sha": "
|
|
6
|
+
"updated": "2026-08-02T19:59:01+02:00",
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
8
|
+
"sha": "3201b6d41437626eb7bf45648c0d7f8c419dc4de87fbd9d4ca83c5ae3b6b6edf"
|
|
9
9
|
}
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;600&family=Lora:wght@400;500&display=swap" rel="stylesheet">
|
|
10
10
|
<style>
|
|
11
11
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
12
|
+
/* An icon never gives up its size to the text beside it: a flex item shrinks by default. */
|
|
13
|
+
svg { flex-shrink: 0; }
|
|
12
14
|
body { font-family: 'Lora', Georgia, serif; background: #faf9f5; padding: 2rem; color: #141413; }
|
|
13
15
|
h1 { font-family: 'Poppins', sans-serif; margin-bottom: 0.5rem; font-size: 1.5rem; }
|
|
14
16
|
.description { color: #b0aea5; margin-bottom: 1.5rem; font-style: italic; max-width: 900px; }
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
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
|
-
"updated": "2026-
|
|
7
|
-
"cliVersion": "1.33.
|
|
8
|
-
"sha": "
|
|
6
|
+
"updated": "2026-08-02T19:58:42+02:00",
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
8
|
+
"sha": "34fc27f140ad425eff742d319b3179f861f70edc966a2cd96d1cd6b91717b766"
|
|
9
9
|
}
|
|
@@ -9,6 +9,7 @@ description: Exhaustive completion discipline for long, complex, or multi-item t
|
|
|
9
9
|
|
|
10
10
|
- Apply to any task with more than a handful of work units: 1:1 ports, language/framework migrations, repo-wide refactors, "implement all X", multi-feature builds, large integrations.
|
|
11
11
|
- Also apply when a single user message bundles multiple distinct asks or questions, even just two, three, or four of them: enumerate every ask up front and cover all of them. Answering the first and silently dropping, postponing, or leaving the rest "pending" is exactly the failure this policy exists to prevent.
|
|
12
|
+
- Also apply when a one-line request generalizes. Once an example has been turned into a rule (core-engineering-policy's Generalization Rule), the sweep for every site that rule touches IS an inventory: enumerate it here and track it in the ledger, instead of fixing whatever the first search happened to surface.
|
|
12
13
|
- Owns inventory, coverage tracking, and completion claims. Subtask decomposition lives in core-engineering-policy; per-change review lives in code-review-policy; test strategy lives in testing-policy.
|
|
13
14
|
|
|
14
15
|
---
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-completion-policy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.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-
|
|
7
|
-
"cliVersion": "1.33.
|
|
8
|
-
"sha": "
|
|
6
|
+
"updated": "2026-08-02T04:15:47+02:00",
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
8
|
+
"sha": "4c2197954135dd3375f32d839238b82a95c0e93e976ddf5a9cbc4a0cf0146e97"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Concise, realistic technical copy - UI microcopy, descriptions, hints, empty/error states, and README/doc prose that informs without over-explaining or restating the obvious, and never uses a typographic dash.",
|
|
6
6
|
"updated": "2026-07-28T20:30:23+02:00",
|
|
7
|
-
"cliVersion": "1.33.
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
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.
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
8
8
|
"sha": "3bdf591057b760f674fb2b1425f63acb426cda2c4f042e1a74c5a5d3807df664"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Strict frontend + backend schema validation, normalization before validation (shared normalizers: trim, lowercase email, capitalize names, canonicalize links and handles), schema consistency, and safe client-facing error handling.",
|
|
6
6
|
"updated": "2026-08-01T17:44:16+02:00",
|
|
7
|
-
"cliVersion": "1.33.
|
|
7
|
+
"cliVersion": "1.33.7",
|
|
8
8
|
"sha": "225e1e26f4a49fac70714b1bebc104ea42143203cc8fcc9e3330b29f05b6e025"
|
|
9
9
|
}
|
package/bin/checksums.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"enigma-darwin-arm64": "
|
|
3
|
-
"enigma-linux-arm64": "
|
|
4
|
-
"enigma-linux-x64": "
|
|
5
|
-
"enigma-win32-x64.exe": "
|
|
2
|
+
"enigma-darwin-arm64": "527b4fd317dd95669889d9e474a1f0dbd926f3dfe69865eaff8f6254fe971256",
|
|
3
|
+
"enigma-linux-arm64": "ed239f141d8628719494ca4563db886a880dbdf3f0f2456bfe0efe77c3918dc6",
|
|
4
|
+
"enigma-linux-x64": "24950b825b7427d9651cf42c85ed29667fbf8309135c7b5bc1fe4811c5f91f99",
|
|
5
|
+
"enigma-win32-x64.exe": "332cc818638fdb0fb94a651af2cfe49da3b1bde40bd151f42624108f4fb374e2"
|
|
6
6
|
}
|
package/dist/guardrails.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { execFileSync } from "child_process";
|
|
7
|
-
import { dirname, join, resolve } from "path";
|
|
7
|
+
import { dirname, join, resolve, sep } from "path";
|
|
8
8
|
import { readFileSync, writeFileSync, statSync, existsSync } from "fs";
|
|
9
9
|
var COMMENT_LINE = /^\s*(\/\/|#|\*|--|<!--|\{?\/\*)/;
|
|
10
10
|
var BUILTIN_RULES = [
|
|
@@ -419,8 +419,13 @@ var BUILTIN_RULES = [
|
|
|
419
419
|
// precision (a multi-line block is not matched: precision > recall).
|
|
420
420
|
pattern: "\\bif\\s*\\(\\s*(isLoading|isPending|isFetching|loading|pending)\\s*\\)\\s*return\\s+(null\\b|<\\s*\\w*(Spinner|Loader|Loading|CircularProgress)\\b)",
|
|
421
421
|
absent: "skeleton|animate-pulse|shimmer|Suspense|ContentLoader|content-loader|<\\s*Placeholder",
|
|
422
|
-
message: "Component returns nothing (or only a spinner) while data loads, so the page stays blank until the fetch resolves. Render the shell
|
|
423
|
-
|
|
422
|
+
message: "Component returns nothing (or only a spinner) while data loads, so the whole page stays blank until the fetch resolves. Render the shell on first paint - nav, headings, card frames, table chrome, filters, and any value you already hold - and skeleton ONLY the region whose data is missing, shaped like the real content with its space reserved so nothing shifts when it lands. A region that does not depend on this request is not loading and must render now (frontend-policy).",
|
|
423
|
+
// BLOCK, changed from warn: this is the rule for the defect users keep reporting (a page
|
|
424
|
+
// that renders nothing until its data arrives), and as a warn it exited 0 - printed to
|
|
425
|
+
// stdout and never fed back to the model, which is precisely why the model kept writing
|
|
426
|
+
// it. Same reasoning as ui-no-em-dash. The pattern is a terse one-line guard cleared by
|
|
427
|
+
// any placeholder signal in the file, so there is no legacy backlog to flag.
|
|
428
|
+
severity: "block",
|
|
424
429
|
skill: "frontend-policy"
|
|
425
430
|
},
|
|
426
431
|
{
|
|
@@ -628,6 +633,21 @@ var BUILTIN_RULES = [
|
|
|
628
633
|
// precise signature (a dirty/hasChanges flag assigned a literal true) returned 0 real
|
|
629
634
|
// hits and 2 false ones, a CLI tracking whether it had rewritten a config file. A rule
|
|
630
635
|
// here would fire on correct code, so it stays guidance in frontend-policy.
|
|
636
|
+
// NOTE: there is deliberately no rule for "a server component awaits its data before it
|
|
637
|
+
// returns markup". It is the other half of the blank-first-paint complaint, but the shape
|
|
638
|
+
// (`export default async function Page()` with an awaited value and no Suspense boundary) is
|
|
639
|
+
// ALSO how a correct statically-generated page is written - the corpus's one match is a docs
|
|
640
|
+
// page awaiting its MDX at build time, where there is no runtime wait to hide. Whether the
|
|
641
|
+
// await costs the user anything depends on where the page renders and whether the data is
|
|
642
|
+
// static, and none of that is in the file. It stays in frontend-policy's Instant First Paint.
|
|
643
|
+
// NOTE: there is deliberately no rule for "a fixed-length one-time code submits itself when
|
|
644
|
+
// the last digit lands". The selector would be precise (`autocomplete="one-time-code"`, the
|
|
645
|
+
// same marker sec-password-breach-check keys on), but the DEFECT has no file-local form: the
|
|
646
|
+
// submit normally lives in a parent, a form library or a mutation hook, so its absence from
|
|
647
|
+
// the field's file proves nothing, and the three guards that make auto-submit safe (once per
|
|
648
|
+
// distinct complete value, no re-fire after a failure, no auto-retry on 429) are behaviour a
|
|
649
|
+
// regex cannot read at all. It stays in frontend-policy's auth section, with the attempt-cap
|
|
650
|
+
// half in security-policy.
|
|
631
651
|
// NOTE: there is deliberately no "card inside a card" or "border with no information"
|
|
632
652
|
// rule, even though both are named in frontend-policy. They are RELATIONAL defects: a
|
|
633
653
|
// container is redundant only relative to the ancestor it sits in and the spacing around
|
|
@@ -697,6 +717,170 @@ var BUILTIN_RULES = [
|
|
|
697
717
|
severity: "block",
|
|
698
718
|
skill: "ciphera-style-policy"
|
|
699
719
|
},
|
|
720
|
+
{
|
|
721
|
+
id: "fe-icon-shrink",
|
|
722
|
+
label: "An icon does not shrink to make room for text",
|
|
723
|
+
files: ["*.css", "*.scss", "*.html", "*.htm", "*.astro", "*.vue", "*.svelte", "*.tsx", "*.jsx"],
|
|
724
|
+
excludeFiles: [
|
|
725
|
+
"*.test.*",
|
|
726
|
+
"*.spec.*",
|
|
727
|
+
"**/tests/**",
|
|
728
|
+
"**/__tests__/**",
|
|
729
|
+
"**/stories/**",
|
|
730
|
+
"*.stories.*",
|
|
731
|
+
"*.min.css",
|
|
732
|
+
"**/dist/**",
|
|
733
|
+
"**/build/**",
|
|
734
|
+
"**/node_modules/**",
|
|
735
|
+
"**/vendor/**",
|
|
736
|
+
"dist/**",
|
|
737
|
+
"build/**",
|
|
738
|
+
"node_modules/**",
|
|
739
|
+
"vendor/**"
|
|
740
|
+
],
|
|
741
|
+
scope: "file",
|
|
742
|
+
// `flex-shrink: 1` is the default, so in a row of icon plus text the browser takes width
|
|
743
|
+
// from BOTH when the text runs long - and the icon, having no content to reflow, is simply
|
|
744
|
+
// squashed. An explicit width/height does not protect it (that is the base size, not the
|
|
745
|
+
// minimum) and an <svg> scales with its viewBox rather than clipping, so it deforms
|
|
746
|
+
// silently instead of overflowing visibly.
|
|
747
|
+
// Two gateable shapes, one per styling model. (a) A STYLESHEET rule sizing an svg/img on
|
|
748
|
+
// one line: the size bound (<= 64px) is what makes it an ICON rather than a picture -
|
|
749
|
+
// a hero image at 640px SHOULD shrink with the viewport, and pinning it would be the
|
|
750
|
+
// wrong fix. (b) A UTILITY-CLASS line carrying a flex container, an icon element and an
|
|
751
|
+
// icon size class together; the flex requirement is what keeps this off the rest of the
|
|
752
|
+
// markup, and it is why a multi-line JSX icon is out of reach by construction (the same
|
|
753
|
+
// accepted recall loss as fe-icon-action-button). Case-SENSITIVE so `[A-Z]\w*` means a
|
|
754
|
+
// component tag (<ExternalLink>, <Avatar>) and not every lowercase element.
|
|
755
|
+
pattern: "^(?!.*enigma:)(?:(?=.*\\bflex\\b)(?=.*<(?:svg|img|[A-Z][A-Za-z0-9]*)\\b).*\\b(?:h-\\d(?:\\.\\d)?[ \\t]+w-\\d(?:\\.\\d)?|w-\\d(?:\\.\\d)?[ \\t]+h-\\d(?:\\.\\d)?|size-\\d(?:\\.\\d)?)\\b|[^{}/]*\\b(?:svg|img)[ \\t]*(?:,[^{}]*)?\\{[^}]*\\bwidth:[ \\t]*(?:[1-9]|[1-5]\\d|6[0-4])px)",
|
|
756
|
+
flags: "",
|
|
757
|
+
// A file that already pins an icon anywhere is treated as having made the decision. This
|
|
758
|
+
// is deliberately leaky (one guarded rule clears the file) because the fix that scales is
|
|
759
|
+
// a single base rule - `svg { flex-shrink: 0 }` - not a repetition per selector, and a
|
|
760
|
+
// rule that kept firing after that fix would train the model to ignore it.
|
|
761
|
+
absent: "flex-shrink:\\s*0|\\bshrink-0\\b|\\bflex-none\\b|flex:\\s*(?:none|0 0)|enigma:allow-shrinking-icon",
|
|
762
|
+
message: "Icon sized but not pinned. `flex-shrink: 1` is the default, so when the text beside it runs long the browser takes width from the ICON too - and having no content to reflow, a 14px glyph ends up rendered 4px wide next to a long name. The explicit width/height does not prevent it: that is the base size, not the minimum, and an svg scales with its viewBox instead of clipping, so it deforms silently. Give anything with a fixed intrinsic size - icon, avatar, badge, status dot, spinner - `flex-shrink: 0` (Tailwind `shrink-0`), and let the TEXT be what truncates. Set it once where the icons are defined (`svg { flex-shrink: 0 }` in the base stylesheet, or inside the shared Icon component) rather than per row. Mark a deliberate exception with an `enigma:` note on the line or `enigma:allow-shrinking-icon` in the file (frontend-policy).",
|
|
763
|
+
severity: "block",
|
|
764
|
+
skill: "frontend-policy"
|
|
765
|
+
},
|
|
766
|
+
// TYPESCRIPT MODULE GRAPH. Three rules that keep a TS project's imports stable as it grows:
|
|
767
|
+
// the project declares an alias, deep climbs go through it, and no specifier carries a build
|
|
768
|
+
// artifact's extension. All three are decided against the project's tsconfig rather than the
|
|
769
|
+
// edited line, which is why each is a coded check (see the module-graph block below).
|
|
770
|
+
{
|
|
771
|
+
id: "ts-alias-paths",
|
|
772
|
+
label: "TypeScript project declares a path alias",
|
|
773
|
+
// The exact basename only: the split configs a bundler generates (tsconfig.node.json,
|
|
774
|
+
// tsconfig.app.json) exist to compile one config file and have no source tree to alias.
|
|
775
|
+
files: ["tsconfig.json"],
|
|
776
|
+
excludeFiles: [
|
|
777
|
+
"**/node_modules/**",
|
|
778
|
+
"**/dist/**",
|
|
779
|
+
"**/build/**",
|
|
780
|
+
"**/vendor/**",
|
|
781
|
+
"node_modules/**",
|
|
782
|
+
"dist/**",
|
|
783
|
+
"build/**",
|
|
784
|
+
"vendor/**"
|
|
785
|
+
],
|
|
786
|
+
scope: "file",
|
|
787
|
+
fileCheck: "ts-alias-paths",
|
|
788
|
+
message: 'This TypeScript project declares no path alias. Add one - `"baseUrl": "."` plus `"paths": { "@/*": ["./src/*"] }` - and import through it (`@/services/user`) instead of counting directories. A relative chain encodes where the importing file happens to sit, so moving either file rewrites specifiers that had nothing to do with the change; an alias is stable under both. Bundlers, tsx and Bun resolve it from tsconfig with no extra config; for Jest add moduleNameMapper. If this config is not the project\'s source config, mark it with an `enigma:` note (ciphera-style-policy).',
|
|
789
|
+
severity: "block",
|
|
790
|
+
skill: "ciphera-style-policy"
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
id: "ts-alias-deep-relative",
|
|
794
|
+
label: "Deep relative import goes through the path alias",
|
|
795
|
+
files: ["*.ts", "*.tsx", "*.mts", "*.cts"],
|
|
796
|
+
// Tests are excluded on purpose: a runner that has not been told about the alias (Jest
|
|
797
|
+
// without moduleNameMapper) cannot resolve it, so the import that is right in src is not
|
|
798
|
+
// automatically right in a test file. Same two-form generated/vendored excludes as above.
|
|
799
|
+
excludeFiles: [
|
|
800
|
+
"*.test.*",
|
|
801
|
+
"*.spec.*",
|
|
802
|
+
"**/tests/**",
|
|
803
|
+
"**/__tests__/**",
|
|
804
|
+
"**/fixtures/**",
|
|
805
|
+
"*.d.ts",
|
|
806
|
+
"**/dist/**",
|
|
807
|
+
"**/build/**",
|
|
808
|
+
"**/_build/**",
|
|
809
|
+
"**/node_modules/**",
|
|
810
|
+
"**/vendor/**",
|
|
811
|
+
"dist/**",
|
|
812
|
+
"build/**",
|
|
813
|
+
"_build/**",
|
|
814
|
+
"node_modules/**",
|
|
815
|
+
"vendor/**"
|
|
816
|
+
],
|
|
817
|
+
scope: "file",
|
|
818
|
+
// Fires only when the project HAS an alias covering the target: the climb on its own is
|
|
819
|
+
// correct code in a project with none, and a target outside the aliased root cannot be
|
|
820
|
+
// written any other way. Measured over the corpus: every project that declares an alias
|
|
821
|
+
// already uses it everywhere, so this is a scaffolding guard, not a backlog.
|
|
822
|
+
fileCheck: "ts-alias-deep-relative",
|
|
823
|
+
message: "Deep relative import in a project that declares a path alias. Write it through the alias instead: the chain of `../` names the directory the importing file sits in today, so moving either file breaks specifiers that had nothing to do with the change, and a reader has to count directories to see what is being imported. Keep `./sibling` and `../` for a file in the same or the parent folder - the alias is for anything further. Mark a deliberate exception with an `enigma:` note on the line (ciphera-style-policy).",
|
|
824
|
+
severity: "block",
|
|
825
|
+
skill: "ciphera-style-policy"
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
id: "ts-import-extension",
|
|
829
|
+
label: "No file extension in a module specifier",
|
|
830
|
+
files: ["*.ts", "*.tsx"],
|
|
831
|
+
// .mts/.cts are out of scope by construction: those extensions exist to pin a file to
|
|
832
|
+
// Node's dual-module resolution, where the specifier extension is mandatory.
|
|
833
|
+
excludeFiles: [
|
|
834
|
+
"*.d.ts",
|
|
835
|
+
"**/dist/**",
|
|
836
|
+
"**/build/**",
|
|
837
|
+
"**/_build/**",
|
|
838
|
+
"**/node_modules/**",
|
|
839
|
+
"**/vendor/**",
|
|
840
|
+
"dist/**",
|
|
841
|
+
"build/**",
|
|
842
|
+
"_build/**",
|
|
843
|
+
"node_modules/**",
|
|
844
|
+
"vendor/**"
|
|
845
|
+
],
|
|
846
|
+
scope: "file",
|
|
847
|
+
// Only under bundler/preserve resolution, and only when no such file actually exists -
|
|
848
|
+
// see extensionImports for why both guards are what keep this at zero false positives.
|
|
849
|
+
fileCheck: "ts-import-extension",
|
|
850
|
+
message: 'File extension in a module specifier. Under `"moduleResolution": "bundler"` the resolver finds the source file on its own, so an extension only pins the import to a build artifact - `.js` names a file that does not exist in the source tree, and `.ts` needs allowImportingTsExtensions and breaks the moment the project emits. Drop it and let the resolver do the work. If this project has to emit for Node\'s own ESM resolution instead, that is a tsconfig decision (`"module": "nodenext"`), and there the extension is required - make it once in tsconfig rather than per import (backend-policy, ciphera-style-policy).',
|
|
851
|
+
severity: "block",
|
|
852
|
+
skill: "ciphera-style-policy"
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
id: "ts-legacy-module-resolution",
|
|
856
|
+
label: "Modern TypeScript module resolution and target",
|
|
857
|
+
files: ["tsconfig.json", "tsconfig.*.json"],
|
|
858
|
+
excludeFiles: [
|
|
859
|
+
"**/node_modules/**",
|
|
860
|
+
"**/dist/**",
|
|
861
|
+
"**/build/**",
|
|
862
|
+
"**/vendor/**",
|
|
863
|
+
"node_modules/**",
|
|
864
|
+
"dist/**",
|
|
865
|
+
"build/**",
|
|
866
|
+
"vendor/**"
|
|
867
|
+
],
|
|
868
|
+
scope: "file",
|
|
869
|
+
// `node`/`node10` is TypeScript's own legacy resolver: it predates package.json "exports",
|
|
870
|
+
// so a modern dependency resolves to the wrong entry point or not at all. A pre-ES2017
|
|
871
|
+
// target is the same class of decision - it downlevels async/await itself. Both are
|
|
872
|
+
// single, unambiguous values, which is what makes this a pattern rule rather than a
|
|
873
|
+
// check; a project that genuinely needs ES5 output marks the line.
|
|
874
|
+
// THE TARGET BOUND IS DELIBERATELY LOWER THAN THE ADVICE. backend-policy asks for es2022,
|
|
875
|
+
// but `"target": "es2017"` is what create-next-app still ships and what several stock
|
|
876
|
+
// configs default to, and in a Next app SWC compiles the output anyway so the value
|
|
877
|
+
// barely matters - blocking the ecosystem's own template is how a rule teaches people to
|
|
878
|
+
// ignore it. The skill persuades toward es2022; the gate only stops what is unambiguous.
|
|
879
|
+
pattern: `^(?!.*enigma:).*(?:["']moduleResolution["']\\s*:\\s*["']node(?:10)?["']|["']target["']\\s*:\\s*["']es(?:3|5|6|2015|2016)["'])`,
|
|
880
|
+
message: 'Legacy TypeScript configuration. `"moduleResolution": "node"` is the pre-2022 resolver: it ignores a package\'s `exports` map, so a modern dependency resolves to the wrong entry point or not at all, and a pre-ES2017 target downlevels async/await itself. For a backend built by a bundler or run by tsx/Bun use `"module": "esnext"` with `"moduleResolution": "bundler"`; for one emitted by tsc for Node\'s own loader use `"module": "nodenext"` (and then specifiers DO carry `.js`). Pair either with `"target": "es2022"` and `"strict": true`. Mark a deliberate legacy target with an `enigma:` note on the line (backend-policy).',
|
|
881
|
+
severity: "block",
|
|
882
|
+
skill: "backend-policy"
|
|
883
|
+
},
|
|
700
884
|
{
|
|
701
885
|
id: "proc-windows-hide",
|
|
702
886
|
label: "Spawned process must not pop a console window",
|
|
@@ -1011,7 +1195,10 @@ var PROJECT_CHECKS = {
|
|
|
1011
1195
|
}
|
|
1012
1196
|
};
|
|
1013
1197
|
var FILE_CHECKS = {
|
|
1014
|
-
"proc-windows-hide": (content) => missingWindowsHide(content)
|
|
1198
|
+
"proc-windows-hide": (content) => missingWindowsHide(content),
|
|
1199
|
+
"ts-import-extension": (content, file) => extensionImports(content, file),
|
|
1200
|
+
"ts-alias-deep-relative": (content, file) => deepRelativeImports(content, file),
|
|
1201
|
+
"ts-alias-paths": (content, file) => missingPathAlias(content, file)
|
|
1015
1202
|
};
|
|
1016
1203
|
var FIXERS = {
|
|
1017
1204
|
"fe-name-input-capitalize": (line, file) => {
|
|
@@ -1082,6 +1269,92 @@ function missingWindowsHide(content) {
|
|
|
1082
1269
|
}
|
|
1083
1270
|
return out;
|
|
1084
1271
|
}
|
|
1272
|
+
var SPECIFIER = /^[ \t]*(?:import|export)\b[^;]*?\bfrom\s*["']([^"']+)["']|^[ \t]*import\s*["']([^"']+)["']|\bimport\(\s*["']([^"']+)["']|\brequire\(\s*["']([^"']+)["']/gm;
|
|
1273
|
+
var MODULE_EXT = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/i;
|
|
1274
|
+
var JS_EXT = /\.(js|jsx|mjs|cjs)$/i;
|
|
1275
|
+
var tsconfigCache = /* @__PURE__ */ new Map();
|
|
1276
|
+
function nearestTsconfig(file) {
|
|
1277
|
+
let dir = dirname(resolve(file));
|
|
1278
|
+
const seen = [];
|
|
1279
|
+
for (let i = 0; i < 20; i++) {
|
|
1280
|
+
const cached = tsconfigCache.get(dir);
|
|
1281
|
+
if (cached !== void 0) {
|
|
1282
|
+
for (const d of seen) tsconfigCache.set(d, cached);
|
|
1283
|
+
return cached;
|
|
1284
|
+
}
|
|
1285
|
+
seen.push(dir);
|
|
1286
|
+
const candidate = join(dir, "tsconfig.json");
|
|
1287
|
+
if (existsSync(candidate)) {
|
|
1288
|
+
let found = null;
|
|
1289
|
+
try {
|
|
1290
|
+
found = { dir, text: readFileSync(candidate, "utf8") };
|
|
1291
|
+
} catch {
|
|
1292
|
+
found = null;
|
|
1293
|
+
}
|
|
1294
|
+
for (const d of seen) tsconfigCache.set(d, found);
|
|
1295
|
+
return found;
|
|
1296
|
+
}
|
|
1297
|
+
const parent = dirname(dir);
|
|
1298
|
+
if (parent === dir) break;
|
|
1299
|
+
dir = parent;
|
|
1300
|
+
}
|
|
1301
|
+
for (const d of seen) tsconfigCache.set(d, null);
|
|
1302
|
+
return null;
|
|
1303
|
+
}
|
|
1304
|
+
function pathAlias(cfg) {
|
|
1305
|
+
const m = /["']([^"']+)\/\*["']\s*:\s*\[\s*["']([^"']+)\/\*["']/.exec(cfg.text);
|
|
1306
|
+
if (!m) return null;
|
|
1307
|
+
const baseUrl = /["']baseUrl["']\s*:\s*["']([^"']+)["']/.exec(cfg.text)?.[1] ?? ".";
|
|
1308
|
+
return { prefix: m[1], root: resolve(cfg.dir, baseUrl, m[2]) };
|
|
1309
|
+
}
|
|
1310
|
+
function specifiers(content) {
|
|
1311
|
+
const lines = content.split("\n");
|
|
1312
|
+
const out = [];
|
|
1313
|
+
for (const m of content.matchAll(SPECIFIER)) {
|
|
1314
|
+
const spec = m[1] ?? m[2] ?? m[3] ?? m[4];
|
|
1315
|
+
if (!spec) continue;
|
|
1316
|
+
const line = content.slice(0, m.index).split("\n").length;
|
|
1317
|
+
const text = lines[line - 1] ?? "";
|
|
1318
|
+
if (COMMENT_LINE.test(text) || text.includes("enigma:")) continue;
|
|
1319
|
+
out.push({ spec, line });
|
|
1320
|
+
}
|
|
1321
|
+
return out;
|
|
1322
|
+
}
|
|
1323
|
+
function extensionImports(content, file) {
|
|
1324
|
+
const cfg = nearestTsconfig(file);
|
|
1325
|
+
if (!cfg || !/["']module(?:Resolution)?["']\s*:\s*["'](?:bundler|preserve)["']/i.test(cfg.text)) return [];
|
|
1326
|
+
const dir = dirname(resolve(file));
|
|
1327
|
+
const out = [];
|
|
1328
|
+
for (const { spec, line } of specifiers(content)) {
|
|
1329
|
+
if (!/^\.\.?\//.test(spec) || !MODULE_EXT.test(spec)) continue;
|
|
1330
|
+
if (JS_EXT.test(spec) && existsSync(resolve(dir, spec))) continue;
|
|
1331
|
+
out.push({ line, detail: `"${spec}" -> "${spec.replace(MODULE_EXT, "")}"` });
|
|
1332
|
+
}
|
|
1333
|
+
return out;
|
|
1334
|
+
}
|
|
1335
|
+
function deepRelativeImports(content, file) {
|
|
1336
|
+
const cfg = nearestTsconfig(file);
|
|
1337
|
+
const alias = cfg && pathAlias(cfg);
|
|
1338
|
+
if (!alias) return [];
|
|
1339
|
+
const dir = dirname(resolve(file));
|
|
1340
|
+
const out = [];
|
|
1341
|
+
for (const { spec, line } of specifiers(content)) {
|
|
1342
|
+
if (!/^(?:\.\.\/){2,}/.test(spec)) continue;
|
|
1343
|
+
const target = resolve(dir, spec);
|
|
1344
|
+
const rel = target.slice(alias.root.length + 1).replace(/\\/g, "/");
|
|
1345
|
+
if (!target.startsWith(`${alias.root}${sep}`) || !rel) continue;
|
|
1346
|
+
out.push({ line, detail: `"${spec}" -> "${alias.prefix}/${rel}"` });
|
|
1347
|
+
}
|
|
1348
|
+
return out;
|
|
1349
|
+
}
|
|
1350
|
+
function missingPathAlias(content, file) {
|
|
1351
|
+
if (/["'](?:paths|extends)["']\s*:/.test(content)) return [];
|
|
1352
|
+
const dir = dirname(resolve(file));
|
|
1353
|
+
const src = ["src", "app", "lib"].find((d) => existsSync(join(dir, d)));
|
|
1354
|
+
if (!src) return [];
|
|
1355
|
+
const anchor = content.split("\n").findIndex((l) => /["']compilerOptions["']/.test(l));
|
|
1356
|
+
return [{ line: anchor === -1 ? 1 : anchor + 1, detail: `no alias for ./${src}` }];
|
|
1357
|
+
}
|
|
1085
1358
|
var NAMED_IMPORT = /^import[ \t]+(?:[\w$]+[ \t]*,[ \t]*)?(?:type[ \t]+)?\{([^}]*)\}[ \t]*from[ \t]*["']([^"']+)["'].*$/gm;
|
|
1086
1359
|
var INTERNAL_MODULE = /^\.|^#|^[@~]\//;
|
|
1087
1360
|
function wideNamedImports(content, max) {
|
|
@@ -1164,7 +1437,7 @@ function checkFile(file, content, projectRoot) {
|
|
|
1164
1437
|
}
|
|
1165
1438
|
} else if (rule.scope === "file" && rule.fileCheck) {
|
|
1166
1439
|
const check = FILE_CHECKS[rule.fileCheck];
|
|
1167
|
-
for (const hit of check ? check(content) : []) {
|
|
1440
|
+
for (const hit of check ? check(content, file) : []) {
|
|
1168
1441
|
out.push({ ...base, line: hit.line, message: `${rule.message} (${hit.detail})` });
|
|
1169
1442
|
}
|
|
1170
1443
|
} else if (rule.scope === "file" && rule.pattern) {
|
|
@@ -1297,9 +1570,12 @@ export {
|
|
|
1297
1570
|
applyFixes,
|
|
1298
1571
|
checkFile,
|
|
1299
1572
|
checkPath,
|
|
1573
|
+
deepRelativeImports,
|
|
1574
|
+
extensionImports,
|
|
1300
1575
|
findProjectRoot,
|
|
1301
1576
|
formatFindings,
|
|
1302
1577
|
loadRules,
|
|
1578
|
+
missingPathAlias,
|
|
1303
1579
|
missingWindowsHide,
|
|
1304
1580
|
runGuardrailsHook,
|
|
1305
1581
|
runGuardrailsScan,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "enigma-cli",
|
|
3
|
-
"version": "1.33.
|
|
3
|
+
"version": "1.33.7",
|
|
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": {
|