enigma-cli 1.30.4 → 1.30.5
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 +16 -4
- package/assets/memory/CLAUDE.md +16 -4
- package/assets/skills/anti-overengineering-policy/skill.json +1 -1
- package/assets/skills/anti-overengineering-review/skill.json +1 -1
- package/assets/skills/backend-policy/skill.json +1 -1
- package/assets/skills/ciphera-style-policy/skill.json +1 -1
- package/assets/skills/code-review-policy/skill.json +1 -1
- package/assets/skills/core-engineering-policy/skill.json +1 -1
- package/assets/skills/database-expert/skill.json +1 -1
- package/assets/skills/debugging-policy/skill.json +1 -1
- package/assets/skills/dependency-policy/skill.json +1 -1
- package/assets/skills/frontend-design/skill.json +1 -1
- package/assets/skills/frontend-policy/skill.json +1 -1
- package/assets/skills/git-policy/skill.json +1 -1
- package/assets/skills/logo-sourcing-policy/skill.json +1 -1
- package/assets/skills/security-policy/skill.json +1 -1
- package/assets/skills/skill-creator/skill.json +1 -1
- package/assets/skills/task-completion-policy/SKILL.md +1 -0
- package/assets/skills/task-completion-policy/skill.json +3 -3
- package/assets/skills/technical-writing-policy/skill.json +1 -1
- package/assets/skills/testing-policy/skill.json +1 -1
- package/assets/skills/validation-policy/skill.json +1 -1
- package/bin/checksums.json +4 -4
- package/bin/enigma.mjs +1 -0
- package/dist/guardrails.js +305 -0
- package/package.json +3 -2
package/assets/memory/AGENTS.md
CHANGED
|
@@ -31,12 +31,24 @@
|
|
|
31
31
|
- End files with exactly one trailing newline and no trailing whitespace.
|
|
32
32
|
- When editing existing code, match its established style instead of imposing a different one.
|
|
33
33
|
|
|
34
|
+
### Engineering Defaults (Always-On)
|
|
35
|
+
|
|
36
|
+
Non-negotiable, language-agnostic defaults - apply them by default without being asked, using the stack's idiomatic tool. They restate the cores of validation-policy, backend-policy and frontend-policy so they hold even when a skill does not load.
|
|
37
|
+
|
|
38
|
+
- 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.
|
|
39
|
+
- Frontend forms: validate in real time against the same schema, and use optimistic UI with rollback on failure for user-facing mutations.
|
|
40
|
+
- 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.
|
|
41
|
+
- 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.
|
|
42
|
+
- Never use the browser's native `alert`/`confirm`/`prompt` - use a dialog/modal component that matches the page design.
|
|
43
|
+
|
|
34
44
|
### Task Execution (Always-On)
|
|
35
45
|
|
|
36
|
-
-
|
|
37
|
-
- Map the dependencies between subtasks
|
|
38
|
-
- 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.
|
|
39
|
-
-
|
|
46
|
+
- 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.
|
|
47
|
+
- 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.
|
|
48
|
+
- 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.
|
|
49
|
+
- "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.
|
|
50
|
+
- 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.
|
|
51
|
+
- 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 grep for TODO/stub markers you introduced. 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.
|
|
40
52
|
- Never offload doable work to the user: "you can adjust/refresh X yourself" in a final report is a hidden deferral. If you can execute the action, do it before reporting; hand off only what genuinely requires the user (credentials, irreversible/destructive choices, business decisions) or what they explicitly approved deferring.
|
|
41
53
|
|
|
42
54
|
<!-- enigma:parallel-subagents:start -->
|
package/assets/memory/CLAUDE.md
CHANGED
|
@@ -31,12 +31,24 @@
|
|
|
31
31
|
- End files with exactly one trailing newline and no trailing whitespace.
|
|
32
32
|
- When editing existing code, match its established style instead of imposing a different one.
|
|
33
33
|
|
|
34
|
+
### Engineering Defaults (Always-On)
|
|
35
|
+
|
|
36
|
+
Non-negotiable, language-agnostic defaults - apply them by default without being asked, using the stack's idiomatic tool. They restate the cores of validation-policy, backend-policy and frontend-policy so they hold even when a skill does not load.
|
|
37
|
+
|
|
38
|
+
- 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.
|
|
39
|
+
- Frontend forms: validate in real time against the same schema, and use optimistic UI with rollback on failure for user-facing mutations.
|
|
40
|
+
- 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.
|
|
41
|
+
- 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.
|
|
42
|
+
- Never use the browser's native `alert`/`confirm`/`prompt` - use a dialog/modal component that matches the page design.
|
|
43
|
+
|
|
34
44
|
### Task Execution (Always-On)
|
|
35
45
|
|
|
36
|
-
-
|
|
37
|
-
- Map the dependencies between subtasks
|
|
38
|
-
- 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.
|
|
39
|
-
-
|
|
46
|
+
- 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.
|
|
47
|
+
- 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.
|
|
48
|
+
- 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.
|
|
49
|
+
- "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.
|
|
50
|
+
- 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.
|
|
51
|
+
- 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 grep for TODO/stub markers you introduced. 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.
|
|
40
52
|
- Never offload doable work to the user: "you can adjust/refresh X yourself" in a final report is a hidden deferral. If you can execute the action, do it before reporting; hand off only what genuinely requires the user (credentials, irreversible/destructive choices, business decisions) or what they explicitly approved deferring.
|
|
41
53
|
|
|
42
54
|
<!-- enigma:parallel-subagents:start -->
|
|
@@ -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.30.
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
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, server-side caching (Redis), and Zod boundary validation.",
|
|
6
6
|
"updated": "2026-06-16T12:06:06+02:00",
|
|
7
|
-
"cliVersion": "1.30.
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
8
8
|
"sha": "a46c3cd00aa5f47adb1e7907f1d2bc6f5562f7a272890dee9b1121976ac04ae1"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Ciphera code style conventions (formatting, naming, imports, comments, code-level anti-patterns; TypeScript-first, language-agnostic).",
|
|
6
6
|
"updated": "2026-06-26T13:40:52+02:00",
|
|
7
|
-
"cliVersion": "1.30.
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
8
8
|
"sha": "dc9ceb784004b05a0117c464e4bed05946835a04acea282586c0b735ee7c2284"
|
|
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.30.
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
8
8
|
"sha": "3d3bbe0602d5bbb4afe37648fe3c2fa39376b1bcbac5d8c441f01fad1e866ed0"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Core engineering execution policy and harness orchestration (highest-authority rules).",
|
|
6
6
|
"updated": "2026-07-16T22:43:53+02:00",
|
|
7
|
-
"cliVersion": "1.30.
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
8
8
|
"sha": "d132f20db08806054d95e58d3d56c2aa7ebc8e6dd24d902b0a0ed9ddfae216c5"
|
|
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.30.
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
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.30.
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
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.30.
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
8
8
|
"sha": "6375d835c2aef2c9bd31ce116444dc3d796f510f9970a213aa3ac4696d7e21b9"
|
|
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-06-25T15:51:43-04:00",
|
|
7
|
-
"cliVersion": "1.30.
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
8
8
|
"sha": "fb78be3233bf9caa67d1f522c19831542b23f47e440b02211b5a950c898318b9"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Frontend architecture: reusable components, abstraction thresholds, state management, no-op save detection, large-list rendering (infinite scroll/pagination, virtualization, skeletons, progressive loading), and optimistic UI with rollback.",
|
|
6
6
|
"updated": "2026-06-26T13:57:02+02:00",
|
|
7
|
-
"cliVersion": "1.30.
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
8
8
|
"sha": "41cb938c9022efcdadc3056417cc7d442511adee40b09a141f11bb5fd9d56514"
|
|
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.30.
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
8
8
|
"sha": "e6dfbc33884000d9d25841bd9c5a84d6558ffd374882cb7b34451eb2cebc2161"
|
|
9
9
|
}
|
|
@@ -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.30.
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
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-06-16T16:39:13+02:00",
|
|
7
|
-
"cliVersion": "1.30.
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
8
8
|
"sha": "699586cce82ec0a5458288b598ee7e5ebdddb3dfcf19db354d8bc5e85e47c1c7"
|
|
9
9
|
}
|
|
@@ -8,6 +8,7 @@ description: Exhaustive completion discipline for long, complex, or multi-item t
|
|
|
8
8
|
## Activation Scope
|
|
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
|
+
- 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.
|
|
11
12
|
- 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.
|
|
12
13
|
|
|
13
14
|
---
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-completion-policy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Exhaustive completion discipline for long/multi-item tasks - inventory, coverage ledger, verified done.",
|
|
6
6
|
"updated": "2026-06-10T22:11:09+02:00",
|
|
7
|
-
"cliVersion": "1.30.
|
|
8
|
-
"sha": "
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
8
|
+
"sha": "feaf44a9b3ab9676c8ebd3c2a799f156d28419998f5a5355250fe95a28cd5a01"
|
|
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.",
|
|
6
6
|
"updated": "2026-06-26T14:09:06+02:00",
|
|
7
|
-
"cliVersion": "1.30.
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
8
8
|
"sha": "e750988b8de51d8a69be621673dceca5413ebad7e1a48e40acd18aad526d9928"
|
|
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.30.
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
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-06-25T18:21:50-04:00",
|
|
7
|
-
"cliVersion": "1.30.
|
|
7
|
+
"cliVersion": "1.30.5",
|
|
8
8
|
"sha": "50349f5b3c55cc337f828dd510c163550614832acb28714871210a7c115a1501"
|
|
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": "f9d771e9f5230cef78d37f428db4f9fd0d5eba50157665153656d5ea15753a0c",
|
|
3
|
+
"enigma-linux-arm64": "b823ffa1f2059dda5ff80559228268ecde03c69ff64240282cb4421caf635509",
|
|
4
|
+
"enigma-linux-x64": "08a0fdb81242947331fb98a2d7f361bdfdd19616fcc6233b830092dbd4444557",
|
|
5
|
+
"enigma-win32-x64.exe": "f59e9668e991f82eb1aeb00237c607a7a81df75fd8f57a3cf7d97c48e7ee70b0"
|
|
6
6
|
}
|
package/bin/enigma.mjs
CHANGED
|
@@ -78,6 +78,7 @@ try {
|
|
|
78
78
|
const env = { ...process.env };
|
|
79
79
|
env.ENIGMA_ASSETS_DIR = join(pkgRoot, "assets");
|
|
80
80
|
env.ENIGMA_GUARD_PATH = join(pkgRoot, "dist", "guard.js");
|
|
81
|
+
env.ENIGMA_GUARDRAILS_PATH = join(pkgRoot, "dist", "guardrails.js");
|
|
81
82
|
try {
|
|
82
83
|
env.ENIGMA_VERSION = packageVersion();
|
|
83
84
|
} catch { /* version is best-effort */ }
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/guardrails.ts
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { execFileSync } from "child_process";
|
|
7
|
+
import { readFileSync, statSync, existsSync } from "fs";
|
|
8
|
+
import { dirname, join, resolve } from "path";
|
|
9
|
+
var COMMENT_LINE = /^\s*(\/\/|#|\*|--|<!--|\{?\/\*)/;
|
|
10
|
+
var BUILTIN_RULES = [
|
|
11
|
+
{
|
|
12
|
+
id: "db-uuid-pk",
|
|
13
|
+
label: "UUID primary keys",
|
|
14
|
+
// Basename globs (no slash) match at any depth, including the repo root; the dir
|
|
15
|
+
// globs additionally catch non-standard extensions under those folders.
|
|
16
|
+
files: ["*.prisma", "*.sql", "*.entity.ts", "**/migrations/**", "**/entities/**", "**/models/**"],
|
|
17
|
+
scope: "file",
|
|
18
|
+
// Matches only explicit auto-increment identity signals across engines/ORMs.
|
|
19
|
+
// Deliberately does NOT match a plain `INTEGER PRIMARY KEY` (valid in many cases,
|
|
20
|
+
// and used on purpose by enigma's own recall SQLite store).
|
|
21
|
+
pattern: `\\b(?:BIG|SMALL)?SERIAL\\b|\\bAUTO_INCREMENT\\b|\\bIDENTITY\\s*\\(|\\bGENERATED\\s+(?:ALWAYS|BY\\s+DEFAULT)\\s+AS\\s+IDENTITY\\b|@default\\(autoincrement\\(\\)\\)|@PrimaryGeneratedColumn\\(\\s*(?:\\)|["']increment["'])`,
|
|
22
|
+
flags: "i",
|
|
23
|
+
message: "Use UUID primary keys, never auto-increment / SERIAL / IDENTITY / AUTO_INCREMENT (database-expert). Generate a UUID (prefer UUIDv7 or ULID) at the application layer or via a database uuid default.",
|
|
24
|
+
severity: "block",
|
|
25
|
+
skill: "database-expert"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "db-ts-orm-prisma",
|
|
29
|
+
label: "Prisma as the default ORM (TypeScript)",
|
|
30
|
+
files: ["package.json", "*.sql", "schema.ts", "ormconfig.*", "data-source.ts", "knexfile.*", "drizzle.config.*"],
|
|
31
|
+
scope: "project",
|
|
32
|
+
check: "ts-relational-no-prisma",
|
|
33
|
+
message: "This is a TypeScript project on a relational datastore without Prisma. Prefer Prisma as the default ORM for new TypeScript work (database-expert).",
|
|
34
|
+
severity: "warn",
|
|
35
|
+
skill: "database-expert"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "be-validate-input-ts",
|
|
39
|
+
label: "Validate request input (TypeScript)",
|
|
40
|
+
files: ["*.ts", "*.js", "*.mts", "*.cts"],
|
|
41
|
+
excludeFiles: ["*.test.*", "*.spec.*", "**/tests/**", "**/__tests__/**"],
|
|
42
|
+
scope: "file",
|
|
43
|
+
// Fires only on ASSIGNING the request BODY to a variable (where validation belongs) with
|
|
44
|
+
// no schema-validation signal in the file. Deliberately NOT req.query/req.params (single
|
|
45
|
+
// scalars, usually validated inline) and NOT a body passed as a bare arg (e.g. to a logger)
|
|
46
|
+
// - real-world scanning showed those are the false-positive sources. The absent set is BROAD
|
|
47
|
+
// (every common validator) so any validated file is skipped: precision over recall.
|
|
48
|
+
pattern: "=\\s*req\\.body\\b|=\\s*(await\\s+)?request\\.json\\(\\)|=\\s*ctx\\.request\\.body\\b|=\\s*await\\s+c\\.req\\.json\\(",
|
|
49
|
+
absent: "z\\.|\\.parse\\(|\\.safeParse\\(|\\.validate\\(|\\.assert\\(|valibot|\\byup\\b|\\bjoi\\b|\\bJoi\\b|\\bajv\\b|superstruct|typebox|@sinclair|arktype|io-ts|runtypes|@Body\\(|class-validator|express-validator|zodResolver|Type\\.Object|checkSchema|celebrate",
|
|
50
|
+
message: "Reads request input without validating it. Parse every input through a schema (Zod, or valibot/yup/...) - never trust an unvalidated shape. For a tagged/event union, validate the discriminant AND that variant's body (validation-policy, backend-policy).",
|
|
51
|
+
severity: "warn",
|
|
52
|
+
skill: "validation-policy"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "be-validate-input-py",
|
|
56
|
+
label: "Validate request input (Python)",
|
|
57
|
+
files: ["*.py"],
|
|
58
|
+
excludeFiles: ["test_*.py", "*_test.py", "conftest.py", "**/tests/**"],
|
|
59
|
+
scope: "file",
|
|
60
|
+
// Assigns the request body to a variable with no schema-validation signal. request.data is
|
|
61
|
+
// intentionally omitted (raw-bytes reads - webhook HMAC, proxying - are not schema surfaces).
|
|
62
|
+
// The absent set is broad to skip any validated file (Pydantic, marshmallow, Django forms, ...).
|
|
63
|
+
pattern: "=\\s*request\\.get_json\\(|=\\s*(await\\s+)?request\\.json\\b|=\\s*request\\.form\\b|=\\s*request\\.POST\\b",
|
|
64
|
+
absent: "BaseModel|pydantic|marshmallow|serializers|TypeAdapter|field_validator|@validator|model_validate|is_valid\\(|forms\\.|ModelForm|cerberus|voluptuous|jsonschema|@dataclass|\\.load\\(|Schema\\(",
|
|
65
|
+
message: "Reads the raw request body without a schema. Validate with Pydantic (BaseModel / model_validate) - or the stack's validator - and discriminate the payload by its type/event (validation-policy, backend-policy).",
|
|
66
|
+
severity: "warn",
|
|
67
|
+
skill: "validation-policy"
|
|
68
|
+
},
|
|
69
|
+
// NOTE: no Go/Rust input-validation rule. Go's manual validation (`if in.X == ""`) is
|
|
70
|
+
// idiomatic and has no detectable signature, and Rust's serde typed deserialization already
|
|
71
|
+
// enforces shape - a rule for either would false-positive. The generic "validate every input"
|
|
72
|
+
// principle for those languages lives in the always-on memory kernel instead.
|
|
73
|
+
{
|
|
74
|
+
id: "fe-password-input",
|
|
75
|
+
label: "Reusable password input (show/hide)",
|
|
76
|
+
files: ["*.tsx", "*.jsx"],
|
|
77
|
+
scope: "file",
|
|
78
|
+
// A raw lowercase <input type="password"> (not a component) with no show/hide toggle in the
|
|
79
|
+
// file. flags:"" = case-sensitive so a capitalized <Input> component is NOT matched; a
|
|
80
|
+
// literal type="password" only, so a dynamic type={visible?...} toggle is not matched either.
|
|
81
|
+
pattern: `<input\\b[^>]*type=["']password["']`,
|
|
82
|
+
flags: "",
|
|
83
|
+
absent: "showPassword|setShowPassword|togglePassword|revealPassword|passwordVisible|isPasswordVisible|showPw|hidePassword",
|
|
84
|
+
message: 'Raw <input type="password">: use the shared reusable Input component (which renders a show/hide toggle for passwords) instead of a bare input, or add the toggle (frontend-policy).',
|
|
85
|
+
severity: "warn",
|
|
86
|
+
skill: "frontend-policy"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "fe-no-native-dialog",
|
|
90
|
+
label: "No native browser dialogs",
|
|
91
|
+
files: ["*.tsx", "*.jsx", "*.ts", "*.js", "*.mts", "*.cts", "*.vue", "*.svelte", "*.astro"],
|
|
92
|
+
excludeFiles: ["*.test.*", "*.spec.*", "**/tests/**", "**/__tests__/**"],
|
|
93
|
+
scope: "file",
|
|
94
|
+
// window.(alert|confirm|prompt)( is unambiguously the native dialog (window is browser-only,
|
|
95
|
+
// so no false positive in a Node file). Bare alert/confirm/prompt is matched ONLY with a
|
|
96
|
+
// string-literal arg - native dialogs take a string, while CLI libs (clack/inquirer) and
|
|
97
|
+
// custom design-system dialogs take a {config} object, and an AI `prompt` value is a string
|
|
98
|
+
// that is passed, not called with a string literal. flags:"" is case-sensitive so a
|
|
99
|
+
// capitalized custom <Alert>/Confirm() is not matched; (?<![.\w]) excludes method calls.
|
|
100
|
+
pattern: `\\bwindow\\.(alert|confirm|prompt)\\s*\\(|(?<![.\\w])(alert|confirm|prompt)\\s*\\(\\s*["']`,
|
|
101
|
+
flags: "",
|
|
102
|
+
message: "Native browser dialog (alert/confirm/prompt) - use a dialog/modal component that matches the page design instead of the browser's built-in (frontend-policy).",
|
|
103
|
+
severity: "warn",
|
|
104
|
+
skill: "frontend-policy"
|
|
105
|
+
}
|
|
106
|
+
];
|
|
107
|
+
var PROJECT_CHECKS = {
|
|
108
|
+
"ts-relational-no-prisma": (root) => {
|
|
109
|
+
const pkg = readPkgDeps(root);
|
|
110
|
+
if (!pkg) return false;
|
|
111
|
+
const hasTs = "typescript" in pkg || existsSync(join(root, "tsconfig.json"));
|
|
112
|
+
if (!hasTs) return false;
|
|
113
|
+
const relational = ["typeorm", "sequelize", "knex", "drizzle-orm", "pg", "mysql", "mysql2", "better-sqlite3", "@mikro-orm/core"];
|
|
114
|
+
if (!relational.some((d) => d in pkg)) return false;
|
|
115
|
+
return !("prisma" in pkg || "@prisma/client" in pkg);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
function readPkgDeps(root) {
|
|
119
|
+
try {
|
|
120
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
121
|
+
return { ...pkg.dependencies, ...pkg.devDependencies, ...pkg.optionalDependencies, ...pkg.peerDependencies };
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function globToRegExp(glob) {
|
|
127
|
+
const esc = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
128
|
+
const body = esc.replace(/\*\*/g, " ").replace(/\*/g, "[^/]*").replace(/ /g, ".*").replace(/\?/g, "[^/]");
|
|
129
|
+
return new RegExp(glob.includes("/") ? `^${body}$` : `(^|/)${body}$`);
|
|
130
|
+
}
|
|
131
|
+
function guardrailsConfigPath() {
|
|
132
|
+
return process.env.ENIGMA_GUARDRAILS_CONFIG || join(homedir(), ".enigma-guardrails.json");
|
|
133
|
+
}
|
|
134
|
+
function isValidRule(r) {
|
|
135
|
+
const x = r;
|
|
136
|
+
if (!x || typeof x.id !== "string" || !Array.isArray(x.files) || typeof x.message !== "string") return false;
|
|
137
|
+
if (x.severity !== "block" && x.severity !== "warn") return false;
|
|
138
|
+
if (x.scope === "file") return typeof x.pattern === "string";
|
|
139
|
+
if (x.scope === "project") return typeof x.check === "string";
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
function loadRules() {
|
|
143
|
+
let disabled = [];
|
|
144
|
+
let custom = [];
|
|
145
|
+
try {
|
|
146
|
+
const raw = JSON.parse(readFileSync(guardrailsConfigPath(), "utf8"));
|
|
147
|
+
if (Array.isArray(raw.disabled)) disabled = raw.disabled.filter((s) => typeof s === "string");
|
|
148
|
+
if (Array.isArray(raw.rules)) custom = raw.rules.filter(isValidRule);
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
const off = new Set(disabled);
|
|
152
|
+
return [...BUILTIN_RULES.filter((r) => !off.has(r.id)), ...custom];
|
|
153
|
+
}
|
|
154
|
+
function findProjectRoot(file) {
|
|
155
|
+
let dir = dirname(resolve(file));
|
|
156
|
+
for (let i = 0; i < 40; i++) {
|
|
157
|
+
const isProj = existsSync(join(dir, "package.json")) || existsSync(join(dir, ".enigma.json"));
|
|
158
|
+
let hasGit = false;
|
|
159
|
+
try {
|
|
160
|
+
hasGit = statSync(join(dir, ".git")).isDirectory();
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
if (isProj || hasGit) return dir;
|
|
164
|
+
const parent = dirname(dir);
|
|
165
|
+
if (parent === dir) break;
|
|
166
|
+
dir = parent;
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
function checkFile(file, content, projectRoot) {
|
|
171
|
+
const norm = file.replace(/\\/g, "/");
|
|
172
|
+
const out = [];
|
|
173
|
+
for (const rule of loadRules()) {
|
|
174
|
+
if (!rule.files.some((g) => globToRegExp(g).test(norm))) continue;
|
|
175
|
+
if (rule.excludeFiles?.some((g) => globToRegExp(g).test(norm))) continue;
|
|
176
|
+
const base = { ruleId: rule.id, severity: rule.severity, file: norm, message: rule.message, skill: rule.skill };
|
|
177
|
+
if (rule.scope === "file" && rule.pattern) {
|
|
178
|
+
if (rule.absent) {
|
|
179
|
+
try {
|
|
180
|
+
if (new RegExp(rule.absent, "i").test(content)) continue;
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
let re;
|
|
185
|
+
try {
|
|
186
|
+
re = new RegExp(rule.pattern, (rule.flags ?? "i").replace(/g/g, ""));
|
|
187
|
+
} catch {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const lines = content.split("\n");
|
|
191
|
+
for (let i = 0; i < lines.length; i++) {
|
|
192
|
+
if (COMMENT_LINE.test(lines[i])) continue;
|
|
193
|
+
if (re.test(lines[i])) out.push({ ...base, line: i + 1 });
|
|
194
|
+
}
|
|
195
|
+
} else if (rule.scope === "project" && rule.check && projectRoot) {
|
|
196
|
+
const check = PROJECT_CHECKS[rule.check];
|
|
197
|
+
if (check && check(projectRoot)) out.push({ ...base });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
function formatFindings(findings) {
|
|
203
|
+
return findings.map((f) => {
|
|
204
|
+
const tag = f.severity === "block" ? "MUST FIX" : "SUGGESTED";
|
|
205
|
+
const loc = f.line ? `:${f.line}` : "";
|
|
206
|
+
const skill = f.skill ? ` [${f.skill}]` : "";
|
|
207
|
+
return `${tag} ${f.file}${loc} (${f.ruleId})${skill}: ${f.message}`;
|
|
208
|
+
}).join("\n");
|
|
209
|
+
}
|
|
210
|
+
function checkPath(file) {
|
|
211
|
+
let content;
|
|
212
|
+
try {
|
|
213
|
+
content = readFileSync(file, "utf8");
|
|
214
|
+
} catch {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
if (content.includes("\0")) return [];
|
|
218
|
+
return checkFile(file, content, findProjectRoot(file));
|
|
219
|
+
}
|
|
220
|
+
function runGuardrailsHook(payload) {
|
|
221
|
+
let file;
|
|
222
|
+
try {
|
|
223
|
+
file = JSON.parse(payload ?? readFileSync(0, "utf8"))?.tool_input?.file_path;
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
if (!file || typeof file !== "string") return 0;
|
|
227
|
+
const findings = checkPath(file);
|
|
228
|
+
if (!findings.length) return 0;
|
|
229
|
+
const warns = findings.filter((f) => f.severity === "warn");
|
|
230
|
+
const blocks = findings.filter((f) => f.severity === "block");
|
|
231
|
+
if (warns.length) process.stdout.write(`enigma guardrails (suggestions)
|
|
232
|
+
${formatFindings(warns)}
|
|
233
|
+
`);
|
|
234
|
+
if (blocks.length) {
|
|
235
|
+
process.stderr.write(`enigma guardrails
|
|
236
|
+
${formatFindings(blocks)}
|
|
237
|
+
Fix the above before continuing.
|
|
238
|
+
`);
|
|
239
|
+
return 2;
|
|
240
|
+
}
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
function gitFiles(all) {
|
|
244
|
+
const out = execFileSync("git", all ? ["ls-files"] : ["diff", "--cached", "--name-only", "--diff-filter=ACM"], { encoding: "utf8" });
|
|
245
|
+
return out.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
246
|
+
}
|
|
247
|
+
function runGuardrailsScan(all) {
|
|
248
|
+
let files;
|
|
249
|
+
try {
|
|
250
|
+
files = gitFiles(all);
|
|
251
|
+
} catch {
|
|
252
|
+
return { ok: true, blocks: [], warns: [], count: 0, notRepo: true };
|
|
253
|
+
}
|
|
254
|
+
const root = process.cwd();
|
|
255
|
+
const blocks = [];
|
|
256
|
+
const warns = [];
|
|
257
|
+
for (const file of files) {
|
|
258
|
+
let content;
|
|
259
|
+
try {
|
|
260
|
+
content = readFileSync(file, "utf8");
|
|
261
|
+
} catch {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (content.includes("\0")) continue;
|
|
265
|
+
for (const f of checkFile(file, content, root)) (f.severity === "block" ? blocks : warns).push(f);
|
|
266
|
+
}
|
|
267
|
+
return { ok: blocks.length === 0, blocks, warns, count: files.length };
|
|
268
|
+
}
|
|
269
|
+
function runGuardrailsScanCli(all) {
|
|
270
|
+
const r = runGuardrailsScan(all);
|
|
271
|
+
if (r.notRepo) {
|
|
272
|
+
console.error("enigma-guardrails: not a git repository; nothing to check.");
|
|
273
|
+
return 0;
|
|
274
|
+
}
|
|
275
|
+
if (r.warns.length) {
|
|
276
|
+
console.error(`enigma-guardrails: ${r.warns.length} suggestion(s):`);
|
|
277
|
+
for (const w of r.warns) console.error(` ! ${formatFindings([w])}`);
|
|
278
|
+
}
|
|
279
|
+
if (r.blocks.length) {
|
|
280
|
+
console.error(`
|
|
281
|
+
enigma-guardrails: BLOCKED - ${r.blocks.length} convention violation(s):`);
|
|
282
|
+
for (const b of r.blocks) console.error(` x ${formatFindings([b])}`);
|
|
283
|
+
console.error("\nTo bypass intentionally for one commit: git commit --no-verify");
|
|
284
|
+
return 1;
|
|
285
|
+
}
|
|
286
|
+
console.log(`enigma-guardrails: ${r.count} ${all ? "tracked" : "staged"} file(s) checked, no blocking violations.`);
|
|
287
|
+
return 0;
|
|
288
|
+
}
|
|
289
|
+
var grEntry = process.argv[1] ?? "";
|
|
290
|
+
var isGrEntry = /(^|[\\/])guardrails\.[mc]?[jt]s$/.test(grEntry);
|
|
291
|
+
if (isGrEntry && fileURLToPath(import.meta.url) === grEntry) {
|
|
292
|
+
process.exit(runGuardrailsScanCli(process.argv.includes("--all")));
|
|
293
|
+
}
|
|
294
|
+
export {
|
|
295
|
+
BUILTIN_RULES,
|
|
296
|
+
PROJECT_CHECKS,
|
|
297
|
+
checkFile,
|
|
298
|
+
checkPath,
|
|
299
|
+
findProjectRoot,
|
|
300
|
+
formatFindings,
|
|
301
|
+
loadRules,
|
|
302
|
+
runGuardrailsHook,
|
|
303
|
+
runGuardrailsScan,
|
|
304
|
+
runGuardrailsScanCli
|
|
305
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "enigma-cli",
|
|
3
|
-
"version": "1.30.
|
|
3
|
+
"version": "1.30.5",
|
|
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": {
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"seal": "tsx src/bin/enigma.ts seal",
|
|
18
18
|
"check": "tsx src/bin/enigma.ts check",
|
|
19
19
|
"guard": "tsx src/guard.ts --all",
|
|
20
|
-
"
|
|
20
|
+
"guardrails": "tsx src/guardrails.ts --all",
|
|
21
|
+
"verify": "npm run typecheck && npm run check && npm run guard && npm run guardrails",
|
|
21
22
|
"prepublishOnly": "npm run verify && npm run build",
|
|
22
23
|
"postinstall": "node bin/postinstall.mjs"
|
|
23
24
|
},
|