enigma-cli 1.36.5 → 1.36.8
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/commands/gate.md +8 -0
- package/assets/skills/anti-overengineering-policy/skill.json +1 -1
- package/assets/skills/anti-overengineering-review/skill.json +1 -1
- package/assets/skills/backend-policy/skill.json +1 -1
- package/assets/skills/ciphera-style-policy/skill.json +1 -1
- package/assets/skills/code-review-policy/skill.json +1 -1
- package/assets/skills/core-engineering-policy/skill.json +1 -1
- package/assets/skills/database-expert/skill.json +1 -1
- package/assets/skills/debugging-policy/skill.json +1 -1
- package/assets/skills/dependency-policy/skill.json +1 -1
- package/assets/skills/email-policy/skill.json +1 -1
- package/assets/skills/frontend-design/skill.json +1 -1
- package/assets/skills/frontend-policy/SKILL.md +1 -0
- package/assets/skills/frontend-policy/skill.json +3 -3
- package/assets/skills/git-policy/skill.json +2 -2
- package/assets/skills/logo-sourcing-policy/skill.json +1 -1
- package/assets/skills/security-policy/skill.json +1 -1
- package/assets/skills/skill-creator/skill.json +1 -1
- package/assets/skills/task-completion-policy/skill.json +1 -1
- package/assets/skills/technical-writing-policy/skill.json +1 -1
- package/assets/skills/testing-policy/skill.json +1 -1
- package/assets/skills/validation-policy/skill.json +1 -1
- package/bin/checksums.json +4 -4
- package/bin/statusline.mjs +62 -17
- package/dist/guardrails.js +75 -0
- package/package.json +1 -1
package/assets/commands/gate.md
CHANGED
|
@@ -115,6 +115,14 @@ What follows from that:
|
|
|
115
115
|
- **One fix round, not three.** Every `--action fix` is two full passes, one to
|
|
116
116
|
apply and one to re-review. Select every finding you intend to fix in a single
|
|
117
117
|
`--findings` list instead of responding once per finding.
|
|
118
|
+
- **When the rounds are the cost, raise `gate-severity`.** It is the lowest
|
|
119
|
+
severity that stops a run to ask you - `error | warning | info`, default
|
|
120
|
+
`warning`. Anything below it is still found and reported, the run just does not
|
|
121
|
+
halt for it. `enigma config gate-severity error -l` is the answer to a diff that
|
|
122
|
+
keeps surfacing one more `info` per round instead of converging. Scope it like
|
|
123
|
+
`/gate off`: `-l` for this project, `-g` only when the user says every project -
|
|
124
|
+
a bare `enigma config` writes the GLOBAL file. Report the change and its scope;
|
|
125
|
+
it is the user's strictness you are lowering.
|
|
118
126
|
- **Propose `--skip test,document` on a throwaway diff** (a typo, a comment, a
|
|
119
127
|
version bump) and let the user decide. Skipping them means no test evidence was
|
|
120
128
|
gathered and no docs were checked, so it is their call - unless they already
|
|
@@ -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.36.
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
8
8
|
"sha": "f742a2be3f328b9ea1ff9a35a449177c2cbec35ad16e46f7054b7a873a2ab017"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Backend/API architecture: controller-service-repository layering, modern TypeScript project configuration (module resolution, strict flags, @/* path alias), API and request optimization (batching, avoiding redundant calls, skipping no-op writes), server-side caching (Redis), and Zod boundary validation.",
|
|
6
6
|
"updated": "2026-08-02T19:59:01+02:00",
|
|
7
|
-
"cliVersion": "1.36.
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
8
8
|
"sha": "652637f818b4ce4f4d5d83fa68fa57fe7842e32fb179523958c5496af54bb938"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Ciphera code style conventions (formatting, naming, imports incl. namespace imports for wide module surfaces, path-alias specifiers and no file extensions, comments, code-level anti-patterns; TypeScript-first, language-agnostic).",
|
|
6
6
|
"updated": "2026-08-02T19:59:01+02:00",
|
|
7
|
-
"cliVersion": "1.36.
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
8
8
|
"sha": "3beb0383a9cb0331ef8a579308a2517d24d57a8ba33751ad17d16d0a4f66e344"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Pre-delivery self-review gate, prioritized review dimensions, and change-quality criteria.",
|
|
6
6
|
"updated": "2026-08-19T12:37:59+02:00",
|
|
7
|
-
"cliVersion": "1.36.
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
8
8
|
"sha": "6b8a924cc11b61d4b45b73384aeb095ce622229359b7cc2c6ce4bb0cf3015ed7"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Senior database architecture policy: engine selection (PostgreSQL by default, SQLite only for local-first/embedded stores), ORM selection (Prisma in TypeScript/JavaScript), query optimization, anti-duplication/normalization, scalability, and RGPD/GDPR encryption.",
|
|
6
6
|
"updated": "2026-08-04T18:33:27+02:00",
|
|
7
|
-
"cliVersion": "1.36.
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
8
8
|
"sha": "26fddc14caf697e830448a2cc453ddc470fe83d098d50bd67b278effba63274e"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Reproduce-isolate-fix debugging methodology with root-cause discipline and regression verification.",
|
|
6
6
|
"updated": "2026-08-02T04:15:47+02:00",
|
|
7
|
-
"cliVersion": "1.36.
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
8
8
|
"sha": "968876bcac9ce6e5a20be05c89ac97191b112c9e08679a8dc506d150c375fec4"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Dependency and supply-chain security: lockfiles and reproducible installs, version pinning, vulnerability auditing, vetting/minimizing packages, vendoring, and SBOM/provenance.",
|
|
6
6
|
"updated": "2026-06-01T00:45:28+02:00",
|
|
7
|
-
"cliVersion": "1.36.
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
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.36.
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
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.36.
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
8
8
|
"sha": "9e30ee7d8a1a1e8c6e7f4e043857cd01841c68a427752e45bc0cad9ec5cfa279"
|
|
9
9
|
}
|
|
@@ -457,6 +457,7 @@ Never render an unbounded or large dataset in one shot (no fetch-everything then
|
|
|
457
457
|
- Pagination (when users need to jump to or deep-link a specific page, the set is bounded, totals/position matter, or results must be SEO-indexable): classic page controls backed by efficient server paging.
|
|
458
458
|
- **Infinite scroll is the preferred default; pagination is the deliberate exception.** Continuous scrolling is what most lists want and what most users now expect, so reach for it first and switch to pages only when one of the reasons above genuinely applies, when the design has nowhere to scroll (a fixed-height panel, a print or export layout, a table meant to be read position by position), or when the user asks for pages. The design and the user's request outrank the default; what does not is skipping the choice and rendering the whole set.
|
|
459
459
|
- Do not infinite-scroll a 30-row admin table or paginate a social feed. A short, bounded list needs neither - just render it (anti-overengineering-policy).
|
|
460
|
+
- A guardrail enforces the part of this that a file can prove: a collection bound from a query or a fetch and then mapped whole, with nothing in the file bounding its size, is blocked (`fe-unbounded-remote-list`). When the collection genuinely is bounded by construction, say so on the line - `enigma:allow-unbounded-list - at most three payment methods per account` - rather than adding paging you do not need. The rule only sees a fetch and a render in the SAME file; a list arriving through a custom hook or a prop is invisible to it and stays your call.
|
|
460
461
|
|
|
461
462
|
### Skeletons while loading
|
|
462
463
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"version": "1.30.1",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Frontend architecture: reusable components, abstraction thresholds, state management, real-time form validation where an emptied or not-yet-filled field is incomplete rather than invalid (a `*` on the label and an aria-disabled submit, never \"email is not valid\" over an empty input), no-op detection (skip any operation whose result equals current state, not just form saves; dirty means different from the loaded snapshot, not touched), instant first paint (render the shell, load data async, skeletons), perceived performance (prefetch on intent, debounce/throttle, cancel stale requests, avoid waterfalls, lazy widgets), large-list rendering (infinite scroll is the preferred default, pagination the deliberate exception when the design or the user calls for it, virtualization, skeletons, progressive loading), data views that ship their own affordances by default (a log, expense, transaction or history table is not done when the rows render: fuse.js search, the filters its column kinds imply, a date range, sort, filter state in the URL, and an export of the filtered set), every reference to an entity being a way into it (a name, id, project or path in a row links to that record, reveals it in a hover card, or at minimum copies and filters by it, never inert text; machine codes get human labels and raw payloads are never dumped into a cell), optimistic UI with rollback, visual restraint (one card level, spacing before borders, one elevation scale), icon actions (repeated row/card actions are icon-only buttons with aria-label plus title, not text labels), responsive/adaptive layout (fluid units, breakpoints, no overlap/overflow, viewport meta, touch targets), form fields that declare their keyboard and casing (autocapitalize/autocomplete/inputmode per field kind, set once in the shared Input, normalized on blur), variable-length text (min-width:0 in flex/grid, wrap vs truncate, long unbroken strings, worst-case content checks), auth screens (breached-password feedback, strength meter, cookie consent before login/register), and AI chat/agent interfaces via Vercel's AI Elements registry instead of hand-rolled message threads.",
|
|
6
|
-
"updated": "2026-08-
|
|
7
|
-
"cliVersion": "1.36.
|
|
8
|
-
"sha": "
|
|
6
|
+
"updated": "2026-08-31T02:47:20+02:00",
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
8
|
+
"sha": "682be5335e0c761c6aa56e8d4825dec0821ca7875e0684b3cdfcc856bc750a2f"
|
|
9
9
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"version": "1.6.0",
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Git & contribution policy (senior engineering standards).",
|
|
6
|
-
"updated": "2026-08-22T09:
|
|
7
|
-
"cliVersion": "1.36.
|
|
6
|
+
"updated": "2026-08-22T09:25:34+02:00",
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
8
8
|
"sha": "cb320a6a5fd3279604ad7920b877c1a36e48f4f39aeead1a4c4daa89901d9ce0"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Application and AI-agent security: secrets, authn/authz (least privilege), credential flows with breach-checked passwords (Have I Been Pwned), OWASP Top 10, transport/crypto baseline, cookies and consent, secure logging, and agent/MCP/tool-use safety.",
|
|
6
6
|
"updated": "2026-08-19T12:37:59+02:00",
|
|
7
|
-
"cliVersion": "1.36.
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
8
8
|
"sha": "0270c33d2b4559e831aec794c188d293d26fc17e2a8064a967d716ceb3a7bab6"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Create new skills, modify and improve existing skills, and measure skill performance with evals and benchmarks.",
|
|
6
6
|
"updated": "2026-08-03T00:16:54+02:00",
|
|
7
|
-
"cliVersion": "1.36.
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
8
8
|
"sha": "a49f6166f57d734e0f13487332da37903e925f32257447856af0fbd6a5f1dc84"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Exhaustive completion discipline for long/multi-item tasks - inventory, coverage ledger, verified done.",
|
|
6
6
|
"updated": "2026-08-02T04:15:47+02:00",
|
|
7
|
-
"cliVersion": "1.36.
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
8
8
|
"sha": "4c2197954135dd3375f32d839238b82a95c0e93e976ddf5a9cbc4a0cf0146e97"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Concise, realistic technical copy - UI microcopy, descriptions, hints, empty/error states, and README/doc prose that informs without over-explaining or restating the obvious, and never uses a typographic dash.",
|
|
6
6
|
"updated": "2026-08-19T12:37:59+02:00",
|
|
7
|
-
"cliVersion": "1.36.
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
8
8
|
"sha": "a5a228a302f50d975a947b9d84676b4c2a4780f2b222f819388cc98d6a12f207"
|
|
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.36.
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
8
8
|
"sha": "3bdf591057b760f674fb2b1425f63acb426cda2c4f042e1a74c5a5d3807df664"
|
|
9
9
|
}
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
"provider": "FJRG2007/enigma",
|
|
5
5
|
"description": "Strict frontend + backend schema validation, normalization before validation (shared normalizers: trim, lowercase email, capitalize names, canonicalize links and handles), real-time error timing (an emptied or not-yet-filled field is incomplete, not invalid: a `*` and an aria-disabled submit instead of \"not valid\" over an empty input), schema consistency, and safe client-facing error handling.",
|
|
6
6
|
"updated": "2026-08-11T17:10:56+02:00",
|
|
7
|
-
"cliVersion": "1.36.
|
|
7
|
+
"cliVersion": "1.36.8",
|
|
8
8
|
"sha": "24928a4c9eed28ebede9f23f746b09a9d3d8176f8fcca61a7780a853fa5bed4e"
|
|
9
9
|
}
|
package/bin/checksums.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"enigma-darwin-arm64": "
|
|
3
|
-
"enigma-linux-arm64": "
|
|
4
|
-
"enigma-linux-x64": "
|
|
5
|
-
"enigma-win32-x64.exe": "
|
|
2
|
+
"enigma-darwin-arm64": "47a9362415fe914b3ccd1356f1c46192c43c352b363be232a0bc20ba83635470",
|
|
3
|
+
"enigma-linux-arm64": "94bc1fa8562891c02b16fe61f2466f57a5072ceda737a6999174aa2ebb542b3c",
|
|
4
|
+
"enigma-linux-x64": "bbbcf79f48df7f65ce7949901da5f0619fe55144befcc053c897b37fcf021c73",
|
|
5
|
+
"enigma-win32-x64.exe": "f95fd67131a856be85109de0325859e6f28207e721692079c971d85b8c672612"
|
|
6
6
|
}
|
package/bin/statusline.mjs
CHANGED
|
@@ -21,7 +21,13 @@ import { join } from "node:path";
|
|
|
21
21
|
import { readFileSync } from "node:fs";
|
|
22
22
|
|
|
23
23
|
/** Snapshot schema this renderer understands; a newer file is ignored, not guessed at. */
|
|
24
|
-
const SNAPSHOT_VERSION =
|
|
24
|
+
const SNAPSHOT_VERSION = 2;
|
|
25
|
+
|
|
26
|
+
/** The pre-map file: a single flat snapshot. Still read during an upgrade, never written. */
|
|
27
|
+
const LEGACY_SNAPSHOT_VERSION = 1;
|
|
28
|
+
|
|
29
|
+
/** Schema version of the code graph's snapshot; a pre-map file carried no version at all. */
|
|
30
|
+
const CODEGRAPH_SNAPSHOT_VERSION = 2;
|
|
25
31
|
|
|
26
32
|
/** Spinner frames, advanced once per second (the status bar's fastest refresh). */
|
|
27
33
|
const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
@@ -160,17 +166,26 @@ function badge(color256) {
|
|
|
160
166
|
* Same reason the gate has one: this is a Node script and the engine is compiled into the Bun
|
|
161
167
|
* binary, so the statusline can never call it. A hook that already ran the engine leaves the two
|
|
162
168
|
* numbers worth showing behind in a small file, and this only reads them.
|
|
169
|
+
*
|
|
170
|
+
* Keyed per repository for the same reason the gate snapshot is, and more urgently: the writer
|
|
171
|
+
* is a session hook, so with several projects open every prompt in one of them used to blank
|
|
172
|
+
* this segment in the others. The deepest matching root wins, so a clone nested inside another
|
|
173
|
+
* repository reads its own counters.
|
|
163
174
|
*/
|
|
164
175
|
export function readCodeGraphStatus(cwd) {
|
|
165
176
|
if (typeof cwd !== "string" || !cwd) return null;
|
|
166
177
|
try {
|
|
167
178
|
const root = process.env.ENIGMA_CODEGRAPH_DIR || join(os.homedir(), ".enigma", "codegraph");
|
|
168
|
-
const
|
|
169
|
-
if (!
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (
|
|
173
|
-
return
|
|
179
|
+
const file = parseJson(readFileSync(join(root, "statusline.json"), "utf8"));
|
|
180
|
+
if (!file) return null;
|
|
181
|
+
let entries;
|
|
182
|
+
if (file.version === CODEGRAPH_SNAPSHOT_VERSION && file.repos && typeof file.repos === "object") entries = Object.values(file.repos);
|
|
183
|
+
else if (file.version === undefined && typeof file.root === "string") entries = [file];
|
|
184
|
+
else return null;
|
|
185
|
+
// Only speak for the repo this session is actually in - another project's counters
|
|
186
|
+
// on this status line would be worse than none.
|
|
187
|
+
const best = deepestEntryFor(entries, cwd, "root");
|
|
188
|
+
return best === null ? null : { symbols: Number(best.symbols) || 0, stale: Number(best.stale) || 0 };
|
|
174
189
|
} catch { return null; }
|
|
175
190
|
}
|
|
176
191
|
|
|
@@ -194,22 +209,52 @@ function pidAlive(pid) {
|
|
|
194
209
|
|
|
195
210
|
/**
|
|
196
211
|
* Loads the gate snapshot for `cwd`, or null when there is nothing to show.
|
|
197
|
-
*
|
|
198
|
-
*
|
|
212
|
+
*
|
|
213
|
+
* The file carries one entry per repository, so a run in another project can no longer
|
|
214
|
+
* take this repository's slot and blank its bar. Where several entries contain `cwd` -
|
|
215
|
+
* a repository checked out inside another one, a worktree, a vendored clone - the
|
|
216
|
+
* deepest root wins, matching how the run ledger resolves the same ambiguity.
|
|
217
|
+
*
|
|
218
|
+
* Rejects an entry that is for another repository, already settled, carries no steps,
|
|
219
|
+
* or was left behind by a daemon that is no longer running. A version-1 file (one flat
|
|
220
|
+
* snapshot, no map) is still read: during an upgrade the renderer is new while the
|
|
221
|
+
* daemon is still the old long-running process, and a dark bar there would look like
|
|
222
|
+
* this very bug.
|
|
199
223
|
*/
|
|
200
224
|
export function readSnapshot(cwd, path = snapshotPath()) {
|
|
201
|
-
let
|
|
225
|
+
let file;
|
|
202
226
|
try {
|
|
203
|
-
|
|
227
|
+
file = parseJson(readFileSync(path, "utf8"));
|
|
204
228
|
} catch {
|
|
205
229
|
return null;
|
|
206
230
|
}
|
|
207
|
-
if (!
|
|
208
|
-
|
|
209
|
-
if (
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
return snap
|
|
231
|
+
if (!file) return null;
|
|
232
|
+
let entries;
|
|
233
|
+
if (file.version === SNAPSHOT_VERSION && file.repos && typeof file.repos === "object") entries = Object.values(file.repos);
|
|
234
|
+
else if (file.version === LEGACY_SNAPSHOT_VERSION) entries = [file];
|
|
235
|
+
else return null;
|
|
236
|
+
return deepestEntryFor(entries, cwd, "repoPath", (snap) =>
|
|
237
|
+
ACTIVE_RUN_STATUSES.has(snap.status) && Array.isArray(snap.steps) && snap.steps.length > 0 && pidAlive(snap.pid));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* The entry of a per-repository snapshot file that owns `cwd`, or null when none does.
|
|
242
|
+
*
|
|
243
|
+
* Both files enigma keeps for this bar - the gate snapshot and the code graph's counters - are
|
|
244
|
+
* maps from a repository root to one entry, and both resolve a cwd the same way: skip an entry
|
|
245
|
+
* whose root does not contain it, then let the DEEPEST root win, so a repository checked out
|
|
246
|
+
* inside another one (a worktree, a vendored clone) reads its own entry rather than its
|
|
247
|
+
* parent's. `accept` carries whatever else that file rejects on.
|
|
248
|
+
*/
|
|
249
|
+
function deepestEntryFor(entries, cwd, key, accept) {
|
|
250
|
+
let best = null;
|
|
251
|
+
for (const entry of entries) {
|
|
252
|
+
if (!entry || typeof entry[key] !== "string") continue;
|
|
253
|
+
if (!isInside(cwd, entry[key])) continue;
|
|
254
|
+
if (accept && !accept(entry)) continue;
|
|
255
|
+
if (best === null || entry[key].length > best[key].length) best = entry;
|
|
256
|
+
}
|
|
257
|
+
return best;
|
|
213
258
|
}
|
|
214
259
|
|
|
215
260
|
/** Reports whether `cwd` is the repo root or a directory beneath it. */
|
package/dist/guardrails.js
CHANGED
|
@@ -755,6 +755,36 @@ var BUILTIN_RULES = [
|
|
|
755
755
|
severity: "block",
|
|
756
756
|
skill: "frontend-policy"
|
|
757
757
|
},
|
|
758
|
+
{
|
|
759
|
+
id: "fe-unbounded-remote-list",
|
|
760
|
+
label: "A list from the server is rendered whole",
|
|
761
|
+
files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro"],
|
|
762
|
+
excludeFiles: ["*.test.*", "*.spec.*", "**/tests/**", "**/__tests__/**", "**/dist/**", "dist/**", "**/build/**", "build/**", "**/.next/**", ".next/**", "**/node_modules/**"],
|
|
763
|
+
scope: "file",
|
|
764
|
+
// The ask: "el modelo no lo integra si no se lo pides". Infinite scroll and pagination
|
|
765
|
+
// live in frontend-policy, a skill that is read when it happens to be, so a screen that
|
|
766
|
+
// will hold thousands of rows ships rendering every one. This is that rule at the tier
|
|
767
|
+
// where it cannot be skipped.
|
|
768
|
+
//
|
|
769
|
+
// MEASURED over 1345 component files of references/repos + packages + apps: 0 findings,
|
|
770
|
+
// 0 false positives, and it fires on both synthetic positives. THE RECALL IS THE HONEST
|
|
771
|
+
// CAVEAT and it is why the skill still owns the convention: only ONE file in that corpus
|
|
772
|
+
// binds a fetched collection in a shape a file-local check can see. Mature apps read
|
|
773
|
+
// their lists through a custom hook or take them as a prop, and the fetch is then several
|
|
774
|
+
// hops away - unknowable from the one file the engine is given. What this DOES cover is
|
|
775
|
+
// the shape an agent writes when it is asked for a screen: fetch and render in the same
|
|
776
|
+
// component, which is exactly the case the report was about.
|
|
777
|
+
//
|
|
778
|
+
// The first cut asked only "does the file fetch" and "does the file map", and flagged 76
|
|
779
|
+
// files - nearly all of them mapping a module constant (STEPS.map) in a component that
|
|
780
|
+
// fetched something unrelated. Tying the rendered list to the binding that produced it is
|
|
781
|
+
// the whole precision story.
|
|
782
|
+
stage: "diff",
|
|
783
|
+
fileCheck: "fe-unbounded-remote-list",
|
|
784
|
+
message: "This list comes from the server and every row of it is rendered, so the screen is only as fast as the smallest dataset anyone tested it with. Bound it: ask the server for a page (cursor or offset) and load the next one as the user reaches the end - virtualized infinite scroll is the default, explicit pagination the deliberate exception when the design or the user calls for it. Whichever you pick, the list needs its loading, empty and end-of-list states. Mark the line `enigma:allow-unbounded-list` when the collection is bounded by construction and say what bounds it (frontend-policy, database-expert).",
|
|
785
|
+
severity: "block",
|
|
786
|
+
skill: "frontend-policy"
|
|
787
|
+
},
|
|
758
788
|
{
|
|
759
789
|
id: "fe-page-await-no-boundary",
|
|
760
790
|
label: "Server route streams its shell before its data",
|
|
@@ -1662,6 +1692,7 @@ var FILE_CHECKS = {
|
|
|
1662
1692
|
"fe-textarea-size-bounds": (content) => textareaSizeBounds(content),
|
|
1663
1693
|
"fe-view-blanked-while-loading": (content) => viewBlankedWhileLoading(content),
|
|
1664
1694
|
"fe-truncated-value-unreachable": (content) => truncatedValueUnreachable(content),
|
|
1695
|
+
"fe-unbounded-remote-list": (content) => unboundedRemoteList(content),
|
|
1665
1696
|
"fe-page-await-no-boundary": (content, file) => pageAwaitWithoutBoundary(content, file),
|
|
1666
1697
|
"ts-import-extension": (content, file) => extensionImports(content, file),
|
|
1667
1698
|
"ts-alias-deep-relative": (content, file) => deepRelativeImports(content, file),
|
|
@@ -1881,6 +1912,48 @@ function viewBlankedWhileLoading(content) {
|
|
|
1881
1912
|
}
|
|
1882
1913
|
return out;
|
|
1883
1914
|
}
|
|
1915
|
+
var REMOTE_COLLECTION_BINDINGS = [
|
|
1916
|
+
/const\s+\{\s*data\s*:\s*(\w+)[^}]*\}\s*=\s*use\w*Query\s*\(/g,
|
|
1917
|
+
/const\s+\{\s*data\s*:\s*(\w+)[^}]*\}\s*=\s*useSWR\s*\(/g,
|
|
1918
|
+
/const\s+\{\s*(data)\s*[,}][^=]*=\s*use\w*Query\s*\(/g,
|
|
1919
|
+
/const\s+\{\s*(data)\s*[,}][^=]*=\s*useSWR\s*\(/g,
|
|
1920
|
+
/const\s+(\w+)\s*=\s*(?:await\s+)?[\w.]*\.findMany\s*\(/g,
|
|
1921
|
+
/const\s+\{\s*data\s*:\s*(\w+)[^}]*\}\s*=\s*await\s+[\w.]*\.from\s*\(/g,
|
|
1922
|
+
/const\s+(\w+)\s*=\s*await\s+[\w.]*\.(?:list|getAll|findAll|scan)\s*\(/g,
|
|
1923
|
+
/const\s+(\w+)\s*=\s*await\s+getDocs\s*\(/g,
|
|
1924
|
+
/const\s+(\w+)\s*=\s*useLoaderData\s*(?:<[^>]*>)?\s*\(/g,
|
|
1925
|
+
/const\s+(\w+)\s*=\s*await\s+\(?\s*await\s+fetch\s*\([^;]*\)\s*\)?\.json\s*\(/g
|
|
1926
|
+
];
|
|
1927
|
+
var LIST_BOUNDED = /useInfiniteQuery|useSWRInfinite|fetchNextPage|hasNextPage|loadMore|load_more|IntersectionObserver|useVirtualizer|react-window|react-virtual\b|virtua\b|VirtualList|Virtuoso|FlatList|RecyclerListView|paginat|pageSize|page_size|perPage|per_page|\bcursor\b|\boffset\b|\.slice\s*\(|\btake\s*:|\blimit\s*:|\bLIMIT\b|\bfirst\s*:|\brange\s*\(/i;
|
|
1928
|
+
var ALLOW_UNBOUNDED_LIST = /enigma:allow-unbounded-list/;
|
|
1929
|
+
function remoteCollectionNames(content) {
|
|
1930
|
+
const names = /* @__PURE__ */ new Set();
|
|
1931
|
+
for (const re of REMOTE_COLLECTION_BINDINGS) {
|
|
1932
|
+
re.lastIndex = 0;
|
|
1933
|
+
let m;
|
|
1934
|
+
while ((m = re.exec(content)) !== null) if (m[1]) names.add(m[1]);
|
|
1935
|
+
}
|
|
1936
|
+
return [...names].filter((n) => !/^[A-Z0-9_]+$/.test(n));
|
|
1937
|
+
}
|
|
1938
|
+
function unboundedRemoteList(content) {
|
|
1939
|
+
const names = remoteCollectionNames(content);
|
|
1940
|
+
if (names.length === 0 || LIST_BOUNDED.test(content)) return [];
|
|
1941
|
+
const alt = names.join("|");
|
|
1942
|
+
const renders = new RegExp(
|
|
1943
|
+
`\\{\\s*(?:${alt})\\b[\\w.?\\[\\]]*\\s*\\.\\s*(?:map|flatMap)\\s*\\(|v-for\\s*=\\s*["'][^"']*\\bin\\s+(?:${alt})\\b|\\{#each\\s+(?:${alt})\\b`
|
|
1944
|
+
);
|
|
1945
|
+
const lines = content.split("\n");
|
|
1946
|
+
const out = [];
|
|
1947
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1948
|
+
const line = lines[i];
|
|
1949
|
+
if (COMMENT_LINE.test(line) || /enigma:/.test(line)) continue;
|
|
1950
|
+
if (!renders.test(line)) continue;
|
|
1951
|
+
if (markedNearby(lines, i, ALLOW_UNBOUNDED_LIST)) continue;
|
|
1952
|
+
out.push({ line: i + 1, detail: `'${names.join("', '")}' comes from the server and every row of it is rendered` });
|
|
1953
|
+
break;
|
|
1954
|
+
}
|
|
1955
|
+
return out;
|
|
1956
|
+
}
|
|
1884
1957
|
var CLIP_ONE_LINE = /\btruncate\b|\btext-ellipsis\b|text-overflow\s*:\s*ellipsis/;
|
|
1885
1958
|
var DYNAMIC_CHILD = /(?<![=!<>-])>\s*\{(?![#/:@])/;
|
|
1886
1959
|
var VALUE_REACHABLE = /\btitle\s*=|aria-label\s*=|<\s*Tooltip|TooltipTrigger|data-tooltip|hoverCard|HoverCard/i;
|
|
@@ -2466,6 +2539,7 @@ export {
|
|
|
2466
2539
|
readLedger,
|
|
2467
2540
|
readReplyLedger,
|
|
2468
2541
|
recordFindings,
|
|
2542
|
+
remoteCollectionNames,
|
|
2469
2543
|
runGuardrailsHook,
|
|
2470
2544
|
runGuardrailsScan,
|
|
2471
2545
|
runGuardrailsScanCli,
|
|
@@ -2473,6 +2547,7 @@ export {
|
|
|
2473
2547
|
summarizeLedger,
|
|
2474
2548
|
textareaSizeBounds,
|
|
2475
2549
|
truncatedValueUnreachable,
|
|
2550
|
+
unboundedRemoteList,
|
|
2476
2551
|
viewBlankedWhileLoading,
|
|
2477
2552
|
wideNamedImports
|
|
2478
2553
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "enigma-cli",
|
|
3
|
-
"version": "1.36.
|
|
3
|
+
"version": "1.36.8",
|
|
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": {
|