@aeptus/aexeo-emdash 0.3.0 → 0.5.0

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/CHANGELOG.md CHANGED
@@ -6,6 +6,105 @@ and the project follows [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.0] - 2026-05-01
10
+
11
+ Three follow-ups from real-world adoption feedback after 0.4.0:
12
+
13
+ - richer suppression selectors so editors can target by collection
14
+ and document status, not just route + rule
15
+ - ergonomic collections API (`includeCollections` /
16
+ `excludeCollections`) for sites whose schema is close to the
17
+ default but needs minor tweaks
18
+ - README now leads with the suppressions glob semantics so consumers
19
+ migrating from local patches don't have to dig through the
20
+ CHANGELOG to learn that `*` is single-segment and `**` is recursive
21
+
22
+ **Compatibility:** verified against emdash `0.7.0` and `0.8.0`.
23
+
24
+ ### Added
25
+
26
+ - **`Suppression.collections`** — silence findings for documents in
27
+ the named emdash collections.
28
+ - **`Suppression.statuses`** — silence findings for documents in the
29
+ named statuses (`"draft"`, `"published"`, etc.). Useful for
30
+ preventing draft-stage findings from cluttering the dashboard.
31
+ - **`includeCollections`** factory option — adds slugs to the
32
+ default `["posts", "pages"]`. For sites that have the defaults
33
+ PLUS extras (e.g. blog adapter + `guides` and `faqs`).
34
+ - **`excludeCollections`** factory option — removes slugs from the
35
+ default. For sites that have most of the defaults but want to
36
+ skip one.
37
+
38
+ ### Changed
39
+
40
+ - **`SuppressionFilter.apply` signature** — now takes a
41
+ `SuppressionContext` object (`{ route, collection?, status? }`)
42
+ instead of a bare route string. Direct callers of `apply` will
43
+ see a TypeScript error and need a one-line update; the public
44
+ factory API (`aexeoPlugin({ suppressions })`) is unchanged.
45
+
46
+ ### Notes for hosts upgrading from 0.4.x
47
+
48
+ - No automatic behavior change. New selectors and options default
49
+ to undefined and are ignored unless the host opts in.
50
+ - Selector matching is **AND across selectors, OR across rules**.
51
+ A suppression with both `routePattern: "/fr-fr/**"` and
52
+ `collections: ["pages"]` only silences findings on French pages,
53
+ not on French blog posts.
54
+ - Sitewide findings (route `*`) ignore `collections` /
55
+ `statuses` selectors — those findings are inherently
56
+ cross-document and have no single collection or status to match.
57
+ Use `routePattern` (or omit it) and `ruleIds` for sitewide
58
+ suppressions.
59
+
60
+ ## [0.4.0] - 2026-05-01
61
+
62
+ Adds upstreamed support for editor-workflow finding suppressions.
63
+ Removes the last reason for consumer sites to maintain a local
64
+ patch over the published plugin.
65
+
66
+ **Compatibility:** verified against emdash `0.7.0` and `0.8.0`.
67
+
68
+ ### Added
69
+
70
+ - **`aexeoPlugin({ suppressions })` option.** Each rule silences
71
+ findings matching the route pattern (glob) AND/OR a rule-id set.
72
+ Applied before findings are persisted to KV — suppressed findings
73
+ never reach the dashboard, /findings, or the per-document panel.
74
+
75
+ ```ts
76
+ aexeoPlugin({
77
+ suppressions: [
78
+ { routePattern: "/privacy", ruleIds: ["RULE001"] },
79
+ { routePattern: "/fr-fr/**", ruleIds: ["RULE002"] },
80
+ { ruleIds: ["RULE999"] }, // applies to every route
81
+ ],
82
+ })
83
+ ```
84
+
85
+ Glob: `*` matches non-`/` chars, `**` matches across `/`, `?`
86
+ matches one non-`/` char. Patterns are anchored. Empty rules
87
+ (`{}` with neither field) are rejected at plugin construction
88
+ with a clear error — that's a kill-switch for all findings
89
+ everywhere and almost certainly an editor mistake.
90
+
91
+ - **`Suppression` type** re-exported from the package root for hosts
92
+ that want to type their config:
93
+
94
+ ```ts
95
+ import type { Suppression } from "@aeptus/aexeo-emdash";
96
+ ```
97
+
98
+ ### Notes for hosts upgrading from 0.3.x
99
+
100
+ - No automatic behavior change. `suppressions` defaults to undefined
101
+ and the filter is a no-op until the host opts in.
102
+ - Suppressions are plugin-only by design. The CLI `check` continues
103
+ to surface every finding; if you want to silence findings at the
104
+ CLI layer, use `aexeo.toml`'s `[ignore]` block. The two surfaces
105
+ serve different audiences: the plugin is editorial workflow, the
106
+ CLI is build-gating.
107
+
9
108
  ## [0.3.0] - 2026-05-01
10
109
 
11
110
  Adds an LLM-assisted authoring flow for the truth manifest
@@ -96,10 +195,10 @@ Block Kit (no link needs there).
96
195
  emdash's `usePluginPage` hook picks this up and renders the
97
196
  React component in place of the Block Kit fallback.
98
197
  - **Two new HTTP routes** the React component consumes:
99
- - `POST /_emdash/api/plugins/aexeo-seogeo/data` — read current
198
+ - `POST /_emdash/api/plugins/aexeo-emdash/data` — read current
100
199
  findings + per-route metadata + computed URLs (no
101
200
  re-evaluation).
102
- - `POST /_emdash/api/plugins/aexeo-seogeo/refresh` — sweep the
201
+ - `POST /_emdash/api/plugins/aexeo-emdash/refresh` — sweep the
103
202
  configured collections, write findings to KV, return the same
104
203
  payload as `/data` plus a refresh summary.
105
204
  Both routes return JSON in the shape exported as `FindingsPayload`
@@ -144,7 +243,7 @@ rule-engine change).
144
243
 
145
244
  ### Added
146
245
 
147
- - **`seogeoPlugin({ collections })` option** for configured mode.
246
+ - **`aexeoPlugin({ collections })` option** for configured mode.
148
247
  Lets sites with non-default collection slugs override which
149
248
  collections the Refresh button sweeps. Defaults to
150
249
  `["posts", "pages"]` when omitted (the slugs from
@@ -182,15 +281,15 @@ First public release.
182
281
 
183
282
  ### Added
184
283
 
185
- - `seogeoPlugin()` factory for **configured mode** — the recommended
284
+ - `aexeoPlugin()` factory for **configured mode** — the recommended
186
285
  install path for first-party emdash sites. Plugin runs in-process
187
286
  inside the host Worker. No sidecar deploy, no auth token, no admin
188
287
  Setup page.
189
- - `seogeoPluginSandboxed({ evaluatorHost })` factory for **sandboxed
288
+ - `aexeoPluginSandboxed({ evaluatorHost })` factory for **sandboxed
190
289
  mode** — preserved for future third-party-deploy scenarios where
191
290
  the plugin code shouldn't have full host access. Requires a
192
291
  separately-deployed sidecar Worker (template at
193
- `seogeo-crawl-worker` in the source repo).
292
+ `aexeo-crawl-worker` in the source repo).
194
293
  - Block Kit admin pages: findings table, per-document panel,
195
294
  intelligence-score dashboard widget. Refresh button on the
196
295
  findings page sweeps every document in the configured collections.
@@ -198,7 +297,7 @@ First public release.
198
297
  automatic re-evaluation of the changed document and update the
199
298
  findings table without manual intervention.
200
299
  - WASM-backed evaluator powered by the `aexeo-emdash-bridge` Rust
201
- crate (same engine as the seogeo CLI). Compiles via the consumer's
300
+ crate (same engine as the Aexeo CLI). Compiles via the consumer's
202
301
  Vite/Wrangler chain — `vite-plugin-wasm` is a peer-side runtime
203
302
  dependency for Vite-driven Cloudflare deploys.
204
303
 
@@ -218,7 +317,9 @@ First public release.
218
317
  `astro.config.mjs` for the WASM import to resolve to a
219
318
  precompiled `WebAssembly.Module`. Not optional.
220
319
 
221
- [Unreleased]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.3.0...HEAD
320
+ [Unreleased]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.5.0...HEAD
321
+ [0.5.0]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.4.0...aexeo-emdash-v0.5.0
322
+ [0.4.0]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.3.0...aexeo-emdash-v0.4.0
222
323
  [0.3.0]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.2.0...aexeo-emdash-v0.3.0
223
324
  [0.2.0]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.2...aexeo-emdash-v0.2.0
224
325
  [0.1.2]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.1...aexeo-emdash-v0.1.2
package/INSTALL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Installing `@aeptus/aexeo-emdash`
2
2
 
3
- This package adds Aexeo's seogeo SEO/GEO content evaluator to an emdash
3
+ This package adds Aexeo SEO/GEO content evaluation to an emdash
4
4
  site. Findings show up as a Block Kit admin page; a sidebar widget
5
5
  shows the site's current intelligence score; saves auto-evaluate the
6
6
  changed document.
@@ -9,11 +9,11 @@ changed document.
9
9
 
10
10
  The package exports two factories:
11
11
 
12
- - **`seogeoPlugin()`** — **configured mode** (recommended). Plugin
12
+ - **`aexeoPlugin()`** — **configured mode** (recommended). Plugin
13
13
  runs in-process inside the host emdash Worker. No separate
14
14
  deployment, no auth token, no admin Setup page. Use this for
15
15
  first-party emdash sites.
16
- - **`seogeoPluginSandboxed({ evaluatorHost })`** — **sandboxed
16
+ - **`aexeoPluginSandboxed({ evaluatorHost })`** — **sandboxed
17
17
  mode**. Plugin runs inside emdash's Worker Loader isolate; WASM
18
18
  evaluation runs in a separate sidecar Worker the operator
19
19
  deploys. Use this only when a third party hosts emdash and
@@ -42,7 +42,7 @@ npm install @aeptus/aexeo-emdash vite-plugin-wasm
42
42
  `vite-plugin-wasm` is required because Vite's defaults treat `.wasm`
43
43
  imports as static-asset URLs, but Cloudflare Workers can only run
44
44
  WASM as precompiled `WebAssembly.Module` instances. The plugin
45
- bridges the two; without it, the seogeo plugin's first call throws
45
+ bridges the two; without it, the Aexeo plugin's first call throws
46
46
  "WASM module did not resolve to a WebAssembly.Module."
47
47
 
48
48
  > **If installing from a private git remote or as a vendored
@@ -54,7 +54,7 @@ bridges the two; without it, the seogeo plugin's first call throws
54
54
  ```js
55
55
  import cloudflare from "@astrojs/cloudflare";
56
56
  import { d1, r2 } from "@emdash-cms/cloudflare";
57
- import { seogeoPlugin } from "@aeptus/aexeo-emdash";
57
+ import { aexeoPlugin } from "@aeptus/aexeo-emdash";
58
58
  import { defineConfig } from "astro/config";
59
59
  import emdash from "emdash/astro";
60
60
  import wasm from "vite-plugin-wasm";
@@ -68,7 +68,7 @@ export default defineConfig({
68
68
  // workerd disallow runtime WebAssembly.instantiate from raw
69
69
  // bytes, so the bundler has to do the compilation.
70
70
  plugins: [wasm()],
71
- // The seogeo plugin's WASM import confuses Vite's dep
71
+ // The Aexeo plugin's WASM import confuses Vite's dep
72
72
  // optimizer when it tries to pre-bundle the package.
73
73
  optimizeDeps: { exclude: ["@aeptus/aexeo-emdash"] },
74
74
  },
@@ -76,7 +76,7 @@ export default defineConfig({
76
76
  emdash({
77
77
  database: d1({ binding: "DB" }),
78
78
  storage: r2({ binding: "MEDIA" }),
79
- plugins: [seogeoPlugin()],
79
+ plugins: [aexeoPlugin()],
80
80
  }),
81
81
  ],
82
82
  });
@@ -96,7 +96,7 @@ no sidecar to deploy.
96
96
  http://localhost:4321/_emdash/admin/plugins
97
97
  ```
98
98
 
99
- The seogeo plugin will appear in the list. Click it.
99
+ The Aexeo plugin will appear in the list. Click it.
100
100
 
101
101
  3. The findings page renders with a Refresh button. Click **Refresh**
102
102
  — toast says "Refreshed N routes (M findings across K documents)"
@@ -151,7 +151,7 @@ token to rotate, no Setup page to revisit.
151
151
 
152
152
  **Verify after a plugin update:**
153
153
 
154
- 1. Visit `/admin/plugins/aexeo-seogeo/findings` and click **Refresh**.
154
+ 1. Visit `/admin/plugins/aexeo-emdash/findings` and click **Refresh**.
155
155
  Toast should show non-zero rules sweeping. (A zero count *can*
156
156
  be valid for a clean site, but if you previously had findings
157
157
  and now have none, suspect the upgrade.)
@@ -216,7 +216,7 @@ Treat anything outside that range as best-effort.
216
216
 
217
217
  ## Removing
218
218
 
219
- Delete the dependency, the import, and the `seogeoPlugin()` line in
219
+ Delete the dependency, the import, and the `aexeoPlugin()` line in
220
220
  `integrations:[emdash({plugins:[...]})]`. The KV keys the plugin
221
221
  wrote remain in your KV namespace — purge with
222
222
  `wrangler kv:key delete --prefix=findings:` and
@@ -243,26 +243,26 @@ Trade-offs vs. configured mode:
243
243
  ```js
244
244
  // astro.config.mjs (sandboxed mode)
245
245
  import { sandbox } from "@emdash-cms/cloudflare";
246
- import { seogeoPluginSandboxed } from "@aeptus/aexeo-emdash";
246
+ import { aexeoPluginSandboxed } from "@aeptus/aexeo-emdash";
247
247
 
248
248
  emdash({
249
249
  database: d1({ binding: "DB" }),
250
250
  storage: r2({ binding: "MEDIA" }),
251
251
  sandboxed: [
252
- seogeoPluginSandboxed({
252
+ aexeoPluginSandboxed({
253
253
  // Public host of the deployed sidecar Worker.
254
- evaluatorHost: "seogeo-crawl-worker.<subdomain>.workers.dev",
254
+ evaluatorHost: "aexeo-crawl-worker.<subdomain>.workers.dev",
255
255
  }),
256
256
  ],
257
257
  sandboxRunner: sandbox(),
258
258
  });
259
259
  ```
260
260
 
261
- The sidecar Worker template lives at `packages/seogeo-crawl-worker/`
261
+ The sidecar Worker template lives at `packages/aexeo-crawl-worker/`
262
262
  in this repo. Per-site deploy:
263
263
 
264
264
  ```bash
265
- cd seogeo-crawl-worker
265
+ cd aexeo-crawl-worker
266
266
  # Edit wrangler.toml: name, R2 bucket name, SITE_URL
267
267
  npx wrangler login
268
268
  npx wrangler r2 bucket create <bucket-name>
@@ -271,7 +271,7 @@ echo "$(openssl rand -hex 32)" | npx wrangler secret put EVAL_TOKEN
271
271
  ```
272
272
 
273
273
  Then in the admin UI, visit
274
- `/admin/plugins/aexeo-seogeo/setup` and paste the deployed URL +
274
+ `/admin/plugins/aexeo-emdash/setup` and paste the deployed URL +
275
275
  the same token.
276
276
 
277
277
  ## Alternative install sources
@@ -316,7 +316,7 @@ present, double-check the package version is recent (≥ the version
316
316
  that switched to direct .wasm imports — see git log for `Switch
317
317
  configured-mode WASM from inlined bytes to direct .wasm import`).
318
318
 
319
- **`seogeo: WASM module did not resolve to a WebAssembly.Module`**
319
+ **`aexeo: WASM module did not resolve to a WebAssembly.Module`**
320
320
  The bundler resolved the .wasm import but produced something other
321
321
  than a Module (URL string, Uint8Array). Same fix as above: ensure
322
322
  `vite-plugin-wasm` is loaded.
@@ -327,7 +327,7 @@ sandboxed two-arg ctx shape. Update; it's fixed in the version with
327
327
  `Switch configured-mode WASM from inlined bytes to direct .wasm
328
328
  import` in the commit log.
329
329
 
330
- **`/admin/plugins` doesn't list seogeo, but `/findings` works.** The
330
+ **`/admin/plugins` doesn't list Aexeo, but `/findings` works.** The
331
331
  emdash version's plugins meta page may filter out plugins without a
332
332
  `/` adminPage entry. The package declares one explicitly; if you see
333
333
  this on a recent version, it's an emdash regression — file upstream.
@@ -344,7 +344,7 @@ Node adapter.
344
344
 
345
345
  ## What the plugin actually does
346
346
 
347
- - **Findings page** (`/_emdash/admin/plugins/aexeo-seogeo/findings`):
347
+ - **Findings page** (`/_emdash/admin/plugins/aexeo-emdash/findings`):
348
348
  Block Kit table of every rule violation across the site. Filter by
349
349
  severity. Click a route in the picker to drill into per-document
350
350
  findings.
@@ -371,6 +371,6 @@ inline. Findings are stored in the host's plugin KV (the
371
371
  `PluginContext.kv` accessor). The Refresh button does the same thing
372
372
  but for every document in every configured collection. The WASM
373
373
  itself is the same `aexeo-emdash-bridge` Rust crate that powers the
374
- seogeo CLI; it's compiled to WebAssembly via wasm-pack and imported
374
+ Aexeo CLI; it's compiled to WebAssembly via wasm-pack and imported
375
375
  by the host's bundler (Vite + `vite-plugin-wasm`). No separate
376
376
  service, no sidecar, no auth token.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Aexeo
3
+ Copyright (c) 2026 Aeptus
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @aeptus/aexeo-emdash
2
2
 
3
- Aexeo's seogeo SEO/GEO content evaluator as an emdash plugin.
3
+ Aexeo SEO/GEO content evaluator as an emdash plugin.
4
4
 
5
5
  Adds a Findings admin page, per-document panel, and dashboard
6
6
  intelligence-score widget to an emdash site. Saves auto-evaluate the
@@ -14,7 +14,7 @@ npm install @aeptus/aexeo-emdash vite-plugin-wasm
14
14
 
15
15
  ```js
16
16
  // astro.config.mjs
17
- import { seogeoPlugin } from "@aeptus/aexeo-emdash";
17
+ import { aexeoPlugin } from "@aeptus/aexeo-emdash";
18
18
  import wasm from "vite-plugin-wasm";
19
19
 
20
20
  export default defineConfig({
@@ -26,7 +26,7 @@ export default defineConfig({
26
26
  emdash({
27
27
  database: d1({ binding: "DB" }),
28
28
  storage: r2({ binding: "MEDIA" }),
29
- plugins: [seogeoPlugin()],
29
+ plugins: [aexeoPlugin()],
30
30
  }),
31
31
  ],
32
32
  });
@@ -40,7 +40,7 @@ troubleshooting list, see [INSTALL.md](./INSTALL.md).
40
40
 
41
41
  ## What it does
42
42
 
43
- - **Findings page** at `/_emdash/admin/plugins/aexeo-seogeo/findings`:
43
+ - **Findings page** at `/_emdash/admin/plugins/aexeo-emdash/findings`:
44
44
  rule violations across the site, filterable by severity.
45
45
  - **Document panel** at `/document`: findings scoped to one document.
46
46
  - **Dashboard widget**: top-line intelligence score (citation, truth,
@@ -59,13 +59,108 @@ troubleshooting list, see [INSTALL.md](./INSTALL.md).
59
59
  bundler handles compilation at build time. `vite-plugin-wasm` is a
60
60
  hard requirement on the consumer side.
61
61
 
62
+ ## Configuring collections
63
+
64
+ The plugin sweeps `["posts", "pages"]` by default — the slugs the
65
+ `@emdash-cms/template-blog-cloudflare` template ships with. Three knobs
66
+ to override, in precedence order:
67
+
68
+ ```js
69
+ // Full override — replace the default entirely.
70
+ // Use when your schema has none of the default slugs.
71
+ aexeoPlugin({ collections: ["blog_posts", "pages"] })
72
+
73
+ // Extend the default — add slugs alongside posts + pages.
74
+ aexeoPlugin({ includeCollections: ["guides", "faqs"] })
75
+
76
+ // Subtract from the default — remove a slug.
77
+ aexeoPlugin({ excludeCollections: ["posts"] })
78
+
79
+ // Combine include + exclude (subtract first, then add).
80
+ // `collections` ignores both — it's the explicit-list override.
81
+ aexeoPlugin({
82
+ excludeCollections: ["posts"],
83
+ includeCollections: ["blog_posts", "guides"],
84
+ })
85
+ ```
86
+
87
+ Pointing any of these at a slug that doesn't exist is non-fatal:
88
+ the bridge's `content.list` returns empty and the missing collection
89
+ shows up in the Refresh summary's `errors` field.
90
+
91
+ ## Suppressing findings
92
+
93
+ When a finding is intentional (legal pages with non-standard SEO,
94
+ localized routes where a rule doesn't apply, draft documents whose
95
+ findings shouldn't surface yet), silence it with a suppression rule.
96
+ Suppressions are applied **before** findings are persisted to KV —
97
+ suppressed findings never reach the dashboard, `/findings`, or the
98
+ per-document panel.
99
+
100
+ ```js
101
+ aexeoPlugin({
102
+ suppressions: [
103
+ // route + rule
104
+ { routePattern: "/privacy", ruleIds: ["RULE001"] },
105
+
106
+ // recursive route subtree
107
+ { routePattern: "/fr-fr/**", ruleIds: ["RULE002"] },
108
+
109
+ // applies to every route
110
+ { ruleIds: ["RULE999"] },
111
+
112
+ // by collection
113
+ { collections: ["drafts"], ruleIds: ["RULE042"] },
114
+
115
+ // by document status
116
+ { statuses: ["draft"] }, // silence ALL findings on drafts
117
+ ],
118
+ })
119
+ ```
120
+
121
+ ### Glob syntax for `routePattern`
122
+
123
+ | Pattern | Matches |
124
+ |---|---|
125
+ | `*` | any chars **except** `/` |
126
+ | `**` | any chars **including** `/` (recursive) |
127
+ | `?` | exactly one char except `/` |
128
+
129
+ Patterns are anchored. `/about` matches only `/about`, not
130
+ `/about/team` — use `/about/**` if you want a subtree.
131
+
132
+ > **Migrating from a local patch**: if your previous setup used
133
+ > looser patterns (e.g. one-asterisk-means-everything), update them
134
+ > on bump. `*` is intentionally single-segment so editors can scope
135
+ > rules to a level of the path hierarchy without recursive surprises.
136
+
137
+ ### Selector semantics
138
+
139
+ A rule may set any combination of `routePattern`, `ruleIds`,
140
+ `collections`, and `statuses`. Matching is **AND across selectors,
141
+ OR across rules**: a finding is suppressed when at least one rule
142
+ matches it; a rule matches when every selector it sets matches the
143
+ finding's context.
144
+
145
+ Empty rules (`{}` with no selectors) are rejected at plugin
146
+ construction. That's a kill-switch for all findings everywhere
147
+ and almost certainly an editor mistake.
148
+
149
+ `collections` and `statuses` only meaningful for per-document
150
+ findings. Sitewide findings (cross-document audit issues) are
151
+ matched only by `routePattern` and `ruleIds`.
152
+
153
+ Suppressions are plugin-only by design. The CLI's `check` is the
154
+ canonical strict audit; if you want to silence findings at the CLI
155
+ layer, use `aexeo.toml`'s `[ignore]` block instead.
156
+
62
157
  ## Two factories
63
158
 
64
159
  The package exports two factories for different trust contexts:
65
160
 
66
- - `seogeoPlugin()` — **configured mode** (recommended). In-process
161
+ - `aexeoPlugin()` — **configured mode** (recommended). In-process
67
162
  plugin. Use for first-party emdash sites.
68
- - `seogeoPluginSandboxed({ evaluatorHost })` — **sandboxed mode**.
163
+ - `aexeoPluginSandboxed({ evaluatorHost })` — **sandboxed mode**.
69
164
  Requires a separately-deployed sidecar Worker. Use only when
70
165
  shipping to third-party emdash sites that shouldn't trust the
71
166
  plugin code with full host access.
package/dist/admin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/admin/Facts.tsx
2
2
  import { useCallback, useEffect, useState } from "react";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
- var PLUGIN_ID = "aexeo-seogeo";
4
+ var PLUGIN_ID = "aexeo-emdash";
5
5
  var API_BASE = "/_emdash/api";
6
6
  function Facts() {
7
7
  const [data, setData] = useState(null);
@@ -275,7 +275,7 @@ async function callRoute(kind, extra) {
275
275
  // src/admin/Findings.tsx
276
276
  import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
277
277
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
278
- var PLUGIN_ID2 = "aexeo-seogeo";
278
+ var PLUGIN_ID2 = "aexeo-emdash";
279
279
  var API_BASE2 = "/_emdash/api";
280
280
  function Findings() {
281
281
  const [state, setState] = useState2({
@@ -1,11 +1,27 @@
1
+ import type { Suppression } from "./suppressions.js";
1
2
  export interface ConfiguredPluginOptions {
2
3
  /**
3
- * emdash collections the Refresh button sweeps. Comes through from
4
- * the descriptor's `options` field — see `seogeoPlugin({ collections })`
5
- * in src/index.ts. When undefined, falls back to DEFAULT_COLLECTIONS
6
- * (`["posts", "pages"]`).
4
+ * Full override of the swept collections. See `aexeoPlugin` in
5
+ * src/index.ts for the full precedence story.
7
6
  */
8
7
  collections?: readonly string[];
8
+ /**
9
+ * Add to the default `["posts", "pages"]`. Ignored when
10
+ * `collections` is set.
11
+ */
12
+ includeCollections?: readonly string[];
13
+ /**
14
+ * Remove from the default `["posts", "pages"]`. Applied before
15
+ * `includeCollections`. Ignored when `collections` is set.
16
+ */
17
+ excludeCollections?: readonly string[];
18
+ /**
19
+ * Editor-workflow suppressions. See `Suppression` in suppressions.ts
20
+ * for the rule shape and matching semantics. Compiled once at plugin
21
+ * construction; the resulting filter is captured in the closures
22
+ * attached to the route handlers and the afterSave hook.
23
+ */
24
+ suppressions?: readonly Suppression[];
9
25
  }
10
26
  export declare function createPlugin(options?: ConfiguredPluginOptions): unknown;
11
27
  export default createPlugin;
@@ -1 +1 @@
1
- {"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"AAiFA,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CAkF3E;AAID,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAmFrD,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEhC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CACvC;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CAyG3E;AAID,eAAe,YAAY,CAAC"}
@@ -73,7 +73,7 @@ function isPortableTextArray(value) {
73
73
  }
74
74
 
75
75
  // src/plugin.ts
76
- async function handleAfterSaveConfigured(event, ctx, evaluator) {
76
+ async function handleAfterSaveConfigured(event, ctx, evaluator, suppressionFilter) {
77
77
  const adapted = adaptContentItem(event.content);
78
78
  const document = adapted.document;
79
79
  const { kv, log } = ctx;
@@ -89,14 +89,23 @@ async function handleAfterSaveConfigured(event, ctx, evaluator) {
89
89
  const pageFindings = result.findings.filter(
90
90
  (finding) => finding.scope === "page"
91
91
  );
92
+ const filtered = suppressionFilter === void 0 ? pageFindings : suppressionFilter.apply(
93
+ {
94
+ route: document.route,
95
+ collection: adapted.meta.collection,
96
+ status: adapted.meta.status
97
+ },
98
+ pageFindings
99
+ );
92
100
  await kv.set(findingsKey(document.route), {
93
101
  route: document.route,
94
- findings: pageFindings
102
+ findings: filtered
95
103
  });
96
104
  }
97
105
  var DEFAULT_COLLECTIONS = ["posts", "pages"];
98
106
  async function evaluateAndPersistAll(ctx, options) {
99
107
  const collections = options.collections ?? DEFAULT_COLLECTIONS;
108
+ const suppressionFilter = options.suppressionFilter;
100
109
  const { kv, log } = ctx;
101
110
  const summary = {
102
111
  documentsScanned: 0,
@@ -138,7 +147,7 @@ async function evaluateAndPersistAll(ctx, options) {
138
147
  }
139
148
  const result = await options.evaluator(documents);
140
149
  if (!result.ok) {
141
- log?.error?.(`seogeo evaluator failure (${result.reason})`, {
150
+ log?.error?.(`aexeo evaluator failure (${result.reason})`, {
142
151
  detail: result.detail
143
152
  });
144
153
  summary.errors.push(`${result.reason}: ${result.detail}`);
@@ -154,11 +163,20 @@ async function evaluateAndPersistAll(ctx, options) {
154
163
  list.push(finding);
155
164
  findingsByRoute.set(bucket, list);
156
165
  }
166
+ let totalAfterSuppression = 0;
157
167
  for (const [route, findings] of findingsByRoute) {
158
- await kv.set(findingsKey(route), { route, findings });
168
+ const stored = adaptedByRoute.get(route);
169
+ const context = {
170
+ route,
171
+ ...stored?.meta.collection === void 0 ? {} : { collection: stored.meta.collection },
172
+ ...stored?.meta.status === void 0 ? {} : { status: stored.meta.status }
173
+ };
174
+ const filtered = suppressionFilter === void 0 ? findings : suppressionFilter.apply(context, findings);
175
+ await kv.set(findingsKey(route), { route, findings: filtered });
159
176
  summary.routesUpdated += 1;
177
+ totalAfterSuppression += filtered.length;
160
178
  }
161
- summary.totalFindings = result.findings.length;
179
+ summary.totalFindings = totalAfterSuppression;
162
180
  return summary;
163
181
  }
164
182
  function findingsKey(route) {
@@ -413,7 +431,7 @@ function ensureInitialized() {
413
431
  }
414
432
  if (!(bridgeModule instanceof WebAssembly.Module)) {
415
433
  throw new Error(
416
- "seogeo: WASM module did not resolve to a WebAssembly.Module \u2014 the consumer's bundler isn't configured for .wasm imports. Add the .wasm loader to your Vite/wrangler config; see @aeptus/aexeo-emdash INSTALL.md for the recipe."
434
+ "aexeo: WASM module did not resolve to a WebAssembly.Module \u2014 the consumer's bundler isn't configured for .wasm imports. Add the .wasm loader to your Vite/wrangler config; see @aeptus/aexeo-emdash INSTALL.md for the recipe."
417
435
  );
418
436
  }
419
437
  const instance = new WebAssembly.Instance(bridgeModule, {
@@ -446,7 +464,8 @@ async function handleDataRoute(ctx, options) {
446
464
  if (options.refresh) {
447
465
  summary = await evaluateAndPersistAll(ctx, {
448
466
  collections: options.collections,
449
- evaluator: options.evaluator
467
+ evaluator: options.evaluator,
468
+ ...options.suppressionFilter === void 0 ? {} : { suppressionFilter: options.suppressionFilter }
450
469
  });
451
470
  await persistManifestStateFindings(ctx);
452
471
  }
@@ -724,11 +743,88 @@ async function handleSave(ctx, body) {
724
743
  return { data: { saved: true } };
725
744
  }
726
745
 
746
+ // src/suppressions.ts
747
+ var NOOP_FILTER = {
748
+ apply: (_context, findings) => [...findings]
749
+ };
750
+ function compileSuppressions(rules) {
751
+ if (rules === void 0 || rules.length === 0) {
752
+ return NOOP_FILTER;
753
+ }
754
+ rules.forEach((rule, index) => {
755
+ const hasRoute = rule.routePattern !== void 0 && rule.routePattern.length > 0;
756
+ const hasRuleIds = rule.ruleIds !== void 0 && rule.ruleIds.length > 0;
757
+ const hasCollections = rule.collections !== void 0 && rule.collections.length > 0;
758
+ const hasStatuses = rule.statuses !== void 0 && rule.statuses.length > 0;
759
+ if (!hasRoute && !hasRuleIds && !hasCollections && !hasStatuses) {
760
+ throw new Error(
761
+ `aexeoPlugin: suppressions[${index}] is empty \u2014 set at least one of routePattern, ruleIds, collections, statuses`
762
+ );
763
+ }
764
+ });
765
+ const compiled = rules.map((rule) => ({
766
+ matchRoute: rule.routePattern === void 0 ? matchAlways : globToMatcher(rule.routePattern),
767
+ ruleIds: rule.ruleIds === void 0 || rule.ruleIds.length === 0 ? null : new Set(rule.ruleIds),
768
+ collections: rule.collections === void 0 || rule.collections.length === 0 ? null : new Set(rule.collections),
769
+ statuses: rule.statuses === void 0 || rule.statuses.length === 0 ? null : new Set(rule.statuses)
770
+ }));
771
+ return {
772
+ apply(context, findings) {
773
+ return findings.filter(
774
+ (finding) => !compiled.some((rule) => {
775
+ if (!rule.matchRoute(context.route)) return false;
776
+ if (rule.ruleIds !== null && !rule.ruleIds.has(finding.rule_id)) {
777
+ return false;
778
+ }
779
+ if (rule.collections !== null) {
780
+ if (context.collection === void 0 || !rule.collections.has(context.collection)) {
781
+ return false;
782
+ }
783
+ }
784
+ if (rule.statuses !== null) {
785
+ if (context.status === void 0 || !rule.statuses.has(context.status)) {
786
+ return false;
787
+ }
788
+ }
789
+ return true;
790
+ })
791
+ );
792
+ }
793
+ };
794
+ }
795
+ var matchAlways = () => true;
796
+ function globToMatcher(pattern) {
797
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
798
+ const DOUBLE_STAR_TOKEN = "";
799
+ const body = escaped.replace(/\*\*/g, DOUBLE_STAR_TOKEN).replace(/\*/g, "[^/]*").replace(new RegExp(DOUBLE_STAR_TOKEN, "g"), ".*").replace(/\?/g, "[^/]");
800
+ const re = new RegExp(`^${body}$`);
801
+ return (route) => re.test(route);
802
+ }
803
+
727
804
  // src/version.ts
728
- var PACKAGE_VERSION = "0.3.0";
805
+ var PACKAGE_VERSION = "0.5.0";
729
806
 
730
807
  // src/configured.ts
731
808
  import { definePlugin } from "emdash";
809
+ function resolveCollections(explicit, include, exclude) {
810
+ if (explicit !== void 0) {
811
+ return [...explicit];
812
+ }
813
+ const excludeSet = new Set(exclude ?? []);
814
+ const base = DEFAULT_COLLECTIONS.filter(
815
+ (slug) => !excludeSet.has(slug)
816
+ );
817
+ if (include === void 0 || include.length === 0) {
818
+ return base;
819
+ }
820
+ const result = [...base];
821
+ for (const slug of include) {
822
+ if (!result.includes(slug)) {
823
+ result.push(slug);
824
+ }
825
+ }
826
+ return result;
827
+ }
732
828
  var inProcessEvaluator = async (documents) => {
733
829
  try {
734
830
  const raw = await evaluateDocuments2(JSON.stringify(documents));
@@ -747,16 +843,26 @@ var inProcessEvaluator = async (documents) => {
747
843
  }
748
844
  };
749
845
  function createPlugin(options = {}) {
750
- const collections = options.collections === void 0 ? DEFAULT_COLLECTIONS : [...options.collections];
846
+ const collections = resolveCollections(
847
+ options.collections,
848
+ options.includeCollections,
849
+ options.excludeCollections
850
+ );
851
+ const suppressionFilter = compileSuppressions(options.suppressions);
751
852
  return definePlugin({
752
- id: "aexeo-seogeo",
853
+ id: "aexeo-emdash",
753
854
  version: PACKAGE_VERSION,
754
855
  capabilities: ["read:content"],
755
856
  hooks: {
756
857
  // afterSave processes one saved document at a time — collections
757
858
  // list isn't needed here. The hook always runs regardless of
758
859
  // which collection the document came from.
759
- "content:afterSave": (event, ctx) => handleAfterSaveConfigured(event, ctx, inProcessEvaluator)
860
+ "content:afterSave": (event, ctx) => handleAfterSaveConfigured(
861
+ event,
862
+ ctx,
863
+ inProcessEvaluator,
864
+ suppressionFilter
865
+ )
760
866
  },
761
867
  routes: {
762
868
  // The Refresh sweep (handleAdminRoute → handleRefresh) is the
@@ -766,8 +872,8 @@ function createPlugin(options = {}) {
766
872
  handler: (ctx) => handleAdminRoute(ctx, collections)
767
873
  },
768
874
  // JSON data endpoint for the React adminEntry. Two routes:
769
- // POST /_emdash/api/plugins/aexeo-seogeo/data — read current findings
770
- // POST /_emdash/api/plugins/aexeo-seogeo/refresh — sweep + read
875
+ // POST /_emdash/api/plugins/aexeo-emdash/data — read current findings
876
+ // POST /_emdash/api/plugins/aexeo-emdash/refresh — sweep + read
771
877
  // Returning JSON (not Block Kit blocks) lets the React
772
878
  // <Findings/> component render proper <a href> links to the
773
879
  // emdash edit page and the public site, which Block Kit can't
@@ -776,14 +882,16 @@ function createPlugin(options = {}) {
776
882
  handler: async (ctx) => (await handleDataRoute(ctx, {
777
883
  collections,
778
884
  evaluator: inProcessEvaluator,
779
- refresh: false
885
+ refresh: false,
886
+ suppressionFilter
780
887
  })).payload
781
888
  },
782
889
  refresh: {
783
890
  handler: (ctx) => handleDataRoute(ctx, {
784
891
  collections,
785
892
  evaluator: inProcessEvaluator,
786
- refresh: true
893
+ refresh: true,
894
+ suppressionFilter
787
895
  })
788
896
  },
789
897
  // /facts route serves the truth-manifest authoring UI. It
@@ -806,7 +914,7 @@ function createPlugin(options = {}) {
806
914
  { path: "/facts", label: "Truth manifest" }
807
915
  ],
808
916
  widgets: [
809
- { id: "seogeo-score", size: "third", title: "SEO score" }
917
+ { id: "aexeo-score", size: "third", title: "SEO score" }
810
918
  ]
811
919
  }
812
920
  });
@@ -845,7 +953,7 @@ async function handlePageLoad(ctx, page) {
845
953
  if (normalized === "" || normalized === "findings") {
846
954
  return renderFindingsPage(ctx);
847
955
  }
848
- if (normalized === "widget:seogeo-score") {
956
+ if (normalized === "widget:aexeo-score") {
849
957
  return renderScoreWidget(ctx);
850
958
  }
851
959
  if (normalized === "document") {
@@ -993,7 +1101,7 @@ async function renderScoreWidget(ctx) {
993
1101
  { type: "header", text: "SEO score" },
994
1102
  {
995
1103
  type: "context",
996
- text: "No documents indexed \u2014 click Refresh on the seogeo findings page."
1104
+ text: "No documents indexed \u2014 click Refresh on the Aexeo findings page."
997
1105
  }
998
1106
  ]
999
1107
  };
@@ -39,6 +39,7 @@ interface DataRouteOptions {
39
39
  collections: readonly string[];
40
40
  evaluator: EvaluatorFn;
41
41
  refresh: boolean;
42
+ suppressionFilter?: import("./suppressions.js").SuppressionFilter;
42
43
  }
43
44
  export declare function handleDataRoute(ctx: SandboxCtx, options: DataRouteOptions): Promise<{
44
45
  payload: FindingsPayload;
@@ -1 +1 @@
1
- {"version":3,"file":"data-route.d.ts","sourceRoot":"","sources":["../src/data-route.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACd,UAAU,EAEX,MAAM,aAAa,CAAC;AAQrB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAM1C,MAAM,WAAW,eAAe;IAG9B,MAAM,EAAE,YAAY,EAAE,CAAC;IAIvB,QAAQ,EAAE,UAAU,EAAE,CAAC;IAIvB,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;IAIF,gBAAgB,EAAE,OAAO,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IAGd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IAIX,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,UAAU,gBAAgB;IACxB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,SAAS,EAAE,WAAW,CAAC;IAIvB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC;IACT,OAAO,EAAE,eAAe,CAAC;IACzB,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;CAChC,CAAC,CAgBD;AAgGD,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,IAAI,CAAC,CAGf"}
1
+ {"version":3,"file":"data-route.d.ts","sourceRoot":"","sources":["../src/data-route.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACd,UAAU,EAEX,MAAM,aAAa,CAAC;AAQrB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAM1C,MAAM,WAAW,eAAe;IAG9B,MAAM,EAAE,YAAY,EAAE,CAAC;IAIvB,QAAQ,EAAE,UAAU,EAAE,CAAC;IAIvB,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;IAIF,gBAAgB,EAAE,OAAO,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IAGd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IAIX,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,UAAU,gBAAgB;IACxB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,SAAS,EAAE,WAAW,CAAC;IAIvB,OAAO,EAAE,OAAO,CAAC;IAIjB,iBAAiB,CAAC,EAAE,OAAO,mBAAmB,EAAE,iBAAiB,CAAC;CACnE;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC;IACT,OAAO,EAAE,eAAe,CAAC;IACzB,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;CAChC,CAAC,CAmBD;AAgGD,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,IAAI,CAAC,CAGf"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { seogeoPluginSandboxed } from "./sandbox.js";
2
- export type { PluginAdminPage, PluginAdminWidget, SandboxedPluginDescriptor, SeogeoSandboxedOptions, } from "./sandbox.js";
1
+ export { aexeoPluginSandboxed } from "./sandbox.js";
2
+ export type { PluginAdminPage, PluginAdminWidget, SandboxedPluginDescriptor, AexeoSandboxedOptions, } from "./sandbox.js";
3
3
  export interface ConfiguredPluginDescriptor {
4
4
  id: string;
5
5
  version: string;
@@ -18,7 +18,7 @@ export interface ConfiguredPluginDescriptor {
18
18
  title?: string;
19
19
  }[];
20
20
  }
21
- export interface SeogeoPluginOptions {
21
+ export interface AexeoPluginOptions {
22
22
  /**
23
23
  * emdash collections the plugin sweeps when an admin clicks
24
24
  * Refresh on the findings page. Order doesn't matter; the sweep
@@ -29,17 +29,80 @@ export interface SeogeoPluginOptions {
29
29
  *
30
30
  * Defaults to `["posts", "pages"]` (the slugs the
31
31
  * `@emdash-cms/template-blog-cloudflare` template ships with).
32
- * Override when your schema uses different collection slugs:
32
+ * Three knobs, in precedence order:
33
33
  *
34
- * seogeoPlugin({ collections: ["posts", "guides", "products"] })
34
+ * - `collections` — full override. When set, replaces the
35
+ * default entirely. Use this when your schema has none of the
36
+ * default slugs (e.g. a site with `blog_posts` and `pages`).
35
37
  *
36
- * Pointing this at a slug that doesn't exist in your schema is
37
- * non-fatal: the bridge's `content.list` returns empty and the
38
- * sweep records the missing collection in the Refresh summary's
39
- * `errors` field. Safe to ship a superset for a project that may
40
- * add collections later.
38
+ * aexeoPlugin({ collections: ["blog_posts", "pages"] })
39
+ *
40
+ * - `includeCollections` — extend the default. Adds slugs to
41
+ * the existing default. Use this when your schema has the
42
+ * default slugs PLUS extras.
43
+ *
44
+ * aexeoPlugin({ includeCollections: ["guides", "faqs"] })
45
+ *
46
+ * - `excludeCollections` — subtract from the default. Removes
47
+ * slugs from the existing default. Use this when your schema
48
+ * has the defaults but you want to skip one.
49
+ *
50
+ * aexeoPlugin({ excludeCollections: ["posts"] })
51
+ *
52
+ * `includeCollections` and `excludeCollections` may be combined
53
+ * (subtract first, then add). `collections` ignores both — it's
54
+ * the explicit-list override.
55
+ *
56
+ * Pointing any of these at a slug that doesn't exist in your
57
+ * schema is non-fatal: the bridge's `content.list` returns empty
58
+ * and the sweep records the missing collection in the Refresh
59
+ * summary's `errors` field. Safe to ship a superset for a project
60
+ * that may add collections later.
41
61
  */
42
62
  collections?: readonly string[];
63
+ /**
64
+ * Slugs to add on top of the default `["posts", "pages"]`. Ignored
65
+ * when `collections` is set (which fully overrides the default).
66
+ * See `collections` for the full precedence story.
67
+ */
68
+ includeCollections?: readonly string[];
69
+ /**
70
+ * Slugs to remove from the default `["posts", "pages"]`. Applied
71
+ * before `includeCollections`, so adding a slug back via include
72
+ * is a no-op (it was never removed). Ignored when `collections`
73
+ * is set.
74
+ */
75
+ excludeCollections?: readonly string[];
76
+ /**
77
+ * Editor-workflow suppressions. Each rule silences findings that
78
+ * match the route pattern (glob) AND/OR are in the rule-id set.
79
+ * Applied before findings are persisted to KV — suppressed findings
80
+ * never reach the dashboard, /findings, or the per-document panel.
81
+ *
82
+ * aexeoPlugin({
83
+ * suppressions: [
84
+ * { routePattern: "/privacy", ruleIds: ["RULE001"] },
85
+ * { routePattern: "/fr-fr/**", ruleIds: ["RULE002"] },
86
+ * { ruleIds: ["RULE999"] }, // applies to every route
87
+ * ],
88
+ * })
89
+ *
90
+ * Glob syntax: `*` matches any chars except `/`; `**` matches across
91
+ * `/`; `?` matches one char except `/`. Patterns are anchored.
92
+ *
93
+ * Empty rules (`{}` with neither field set) are rejected at plugin
94
+ * construction — that's a kill-switch for all findings everywhere
95
+ * and almost certainly an editor mistake.
96
+ *
97
+ * Suppressions are plugin-only by design. The CLI's `check` is the
98
+ * canonical strict audit; if you want to silence findings at the
99
+ * CLI layer, use `aexeo.toml`'s `[ignore]` block instead. The two
100
+ * surfaces serve different audiences: the plugin is editorial
101
+ * workflow, the CLI is build-gating.
102
+ */
103
+ suppressions?: readonly Suppression[];
43
104
  }
44
- export declare function seogeoPlugin(options?: SeogeoPluginOptions): ConfiguredPluginDescriptor;
105
+ export type { Suppression } from "./suppressions.js";
106
+ import type { Suppression } from "./suppressions.js";
107
+ export declare function aexeoPlugin(options?: AexeoPluginOptions): ConfiguredPluginDescriptor;
45
108
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAYtB,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IAOnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,UAAU,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,YAAY,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACxE;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,wBAAgB,YAAY,CAC1B,OAAO,GAAE,mBAAwB,GAChC,0BAA0B,CA+C5B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAYtB,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IAOnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,UAAU,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,YAAY,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACxE;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEhC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,YAAY,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CACvC;AAED,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,wBAAgB,WAAW,CACzB,OAAO,GAAE,kBAAuB,GAC/B,0BAA0B,CAqE5B"}
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ var baseCapabilities = [
6
6
  "write:artifacts:public/llms-full.txt",
7
7
  "write:artifacts:public/facts.json",
8
8
  "write:artifacts:public/*.md.txt",
9
- "kv:seogeo-baselines"
9
+ "kv:aexeo-baselines"
10
10
  ];
11
11
  function buildCapabilities(evaluatorHost) {
12
12
  if (evaluatorHost === null) {
@@ -23,13 +23,13 @@ function buildAllowedHosts(evaluatorHost) {
23
23
  }
24
24
 
25
25
  // src/version.ts
26
- var PACKAGE_VERSION = "0.3.0";
26
+ var PACKAGE_VERSION = "0.5.0";
27
27
 
28
28
  // src/sandbox.ts
29
- function seogeoPluginSandboxed(options = {}) {
30
- const evaluatorHost = options.evaluatorHost ?? process.env.SEOGEO_EVALUATOR_HOST ?? null;
29
+ function aexeoPluginSandboxed(options = {}) {
30
+ const evaluatorHost = options.evaluatorHost ?? process.env.AEXEO_EVALUATOR_HOST ?? null;
31
31
  return {
32
- id: "aexeo-seogeo",
32
+ id: "aexeo-emdash",
33
33
  version: PACKAGE_VERSION,
34
34
  // Subpath export of this same package; must match package.json
35
35
  // `exports["./sandbox"]`.
@@ -37,7 +37,7 @@ function seogeoPluginSandboxed(options = {}) {
37
37
  format: "standard",
38
38
  capabilities: buildCapabilities(evaluatorHost),
39
39
  allowedHosts: buildAllowedHosts(evaluatorHost),
40
- // Pages mount at /admin/plugins/aexeo-seogeo/<path>. The page name
40
+ // Pages mount at /admin/plugins/aexeo-emdash/<path>. The page name
41
41
  // (without leading slash) is what the sandbox entry receives in
42
42
  // `body.page` when emdash POSTs the page_load interaction.
43
43
  adminPages: [
@@ -46,15 +46,15 @@ function seogeoPluginSandboxed(options = {}) {
46
46
  { path: "/setup", label: "Setup" }
47
47
  ],
48
48
  adminWidgets: [
49
- { id: "seogeo-score", size: "third", title: "SEO score" }
49
+ { id: "aexeo-score", size: "third", title: "SEO score" }
50
50
  ]
51
51
  };
52
52
  }
53
53
 
54
54
  // src/index.ts
55
- function seogeoPlugin(options = {}) {
55
+ function aexeoPlugin(options = {}) {
56
56
  return {
57
- id: "aexeo-seogeo",
57
+ id: "aexeo-emdash",
58
58
  version: PACKAGE_VERSION,
59
59
  // Subpath import resolved by the consumer's bundler at build
60
60
  // time — must match the package.json `exports["./configured"]`
@@ -75,12 +75,22 @@ function seogeoPlugin(options = {}) {
75
75
  // receives. Spread to a plain object so readonly arrays survive
76
76
  // the structured-clone semantics of that codegen.
77
77
  options: {
78
- ...options.collections === void 0 ? {} : { collections: [...options.collections] }
78
+ ...options.collections === void 0 ? {} : { collections: [...options.collections] },
79
+ ...options.includeCollections === void 0 ? {} : { includeCollections: [...options.includeCollections] },
80
+ ...options.excludeCollections === void 0 ? {} : { excludeCollections: [...options.excludeCollections] },
81
+ ...options.suppressions === void 0 ? {} : {
82
+ suppressions: options.suppressions.map((rule) => ({
83
+ ...rule,
84
+ ...rule.ruleIds === void 0 ? {} : { ruleIds: [...rule.ruleIds] },
85
+ ...rule.collections === void 0 ? {} : { collections: [...rule.collections] },
86
+ ...rule.statuses === void 0 ? {} : { statuses: [...rule.statuses] }
87
+ }))
88
+ }
79
89
  },
80
90
  capabilities: [
81
91
  "read:content",
82
92
  "read:schema",
83
- "kv:seogeo-baselines"
93
+ "kv:aexeo-baselines"
84
94
  ],
85
95
  // adminPages drives the admin sidebar — one nav entry per item.
86
96
  // Don't list "/" alongside "/findings": both would render as
@@ -94,11 +104,11 @@ function seogeoPlugin(options = {}) {
94
104
  { path: "/document", label: "Document SEO" }
95
105
  ],
96
106
  adminWidgets: [
97
- { id: "seogeo-score", size: "third", title: "SEO score" }
107
+ { id: "aexeo-score", size: "third", title: "SEO score" }
98
108
  ]
99
109
  };
100
110
  }
101
111
  export {
102
- seogeoPlugin,
103
- seogeoPluginSandboxed
112
+ aexeoPlugin,
113
+ aexeoPluginSandboxed
104
114
  };
package/dist/plugin.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { SuppressionFilter } from "./suppressions.js";
1
2
  import type { EmdashDocument, Finding } from "./types.js";
2
3
  import { type EmdashContentItem, type EmdashContentMeta } from "./adapter.js";
3
4
  import type { SidecarHttp } from "./sidecar.js";
@@ -58,7 +59,7 @@ export interface ContentAfterSaveEvent {
58
59
  isNew: boolean;
59
60
  }
60
61
  export declare function handleAfterSave(_event: ContentAfterSaveEvent, _ctx: SandboxCtx): Promise<void>;
61
- export declare function handleAfterSaveConfigured(event: ContentAfterSaveEvent, ctx: SandboxCtx, evaluator: EvaluatorFn): Promise<void>;
62
+ export declare function handleAfterSaveConfigured(event: ContentAfterSaveEvent, ctx: SandboxCtx, evaluator: EvaluatorFn, suppressionFilter?: SuppressionFilter): Promise<void>;
62
63
  export declare const DEFAULT_COLLECTIONS: readonly ["posts", "pages"];
63
64
  export interface RefreshSummary {
64
65
  documentsScanned: number;
@@ -78,6 +79,7 @@ export type EvaluatorFn = (documents: readonly EmdashDocument[]) => Promise<Eval
78
79
  export declare function evaluateAndPersistAll(ctx: SandboxCtx, options: {
79
80
  collections?: readonly string[];
80
81
  evaluator: EvaluatorFn;
82
+ suppressionFilter?: SuppressionFilter;
81
83
  }): Promise<RefreshSummary>;
82
84
  export declare function findingsKey(route: string): string;
83
85
  export declare function documentKey(route: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
@@ -89,6 +89,7 @@ async function handleAfterSave(_event, _ctx) {
89
89
  var DEFAULT_COLLECTIONS = ["posts", "pages"];
90
90
  async function evaluateAndPersistAll(ctx, options) {
91
91
  const collections = options.collections ?? DEFAULT_COLLECTIONS;
92
+ const suppressionFilter = options.suppressionFilter;
92
93
  const { kv, log } = ctx;
93
94
  const summary = {
94
95
  documentsScanned: 0,
@@ -130,7 +131,7 @@ async function evaluateAndPersistAll(ctx, options) {
130
131
  }
131
132
  const result = await options.evaluator(documents);
132
133
  if (!result.ok) {
133
- log?.error?.(`seogeo evaluator failure (${result.reason})`, {
134
+ log?.error?.(`aexeo evaluator failure (${result.reason})`, {
134
135
  detail: result.detail
135
136
  });
136
137
  summary.errors.push(`${result.reason}: ${result.detail}`);
@@ -146,11 +147,20 @@ async function evaluateAndPersistAll(ctx, options) {
146
147
  list.push(finding);
147
148
  findingsByRoute2.set(bucket, list);
148
149
  }
150
+ let totalAfterSuppression = 0;
149
151
  for (const [route, findings] of findingsByRoute2) {
150
- await kv.set(findingsKey(route), { route, findings });
152
+ const stored = adaptedByRoute.get(route);
153
+ const context = {
154
+ route,
155
+ ...stored?.meta.collection === void 0 ? {} : { collection: stored.meta.collection },
156
+ ...stored?.meta.status === void 0 ? {} : { status: stored.meta.status }
157
+ };
158
+ const filtered = suppressionFilter === void 0 ? findings : suppressionFilter.apply(context, findings);
159
+ await kv.set(findingsKey(route), { route, findings: filtered });
151
160
  summary.routesUpdated += 1;
161
+ totalAfterSuppression += filtered.length;
152
162
  }
153
- summary.totalFindings = result.findings.length;
163
+ summary.totalFindings = totalAfterSuppression;
154
164
  return summary;
155
165
  }
156
166
  function findingsKey(route) {
@@ -360,8 +370,8 @@ function failure(reason, rejected) {
360
370
 
361
371
  // src/mcp.ts
362
372
  var checkTool = {
363
- name: "seogeo.check",
364
- description: "Evaluate a list of EmdashDocument values against the seogeo rule engine and return the stable Finding contract. Mirrors the CLI's `seogeo-cli check` surface so agents, the admin UI, and the CI gate all see the same rule ids.",
373
+ name: "aexeo.check",
374
+ description: "Evaluate a list of EmdashDocument values against the Aexeo rule engine and return the stable Finding contract. Mirrors the CLI's `aexeo-cli check` surface so agents, the admin UI, and the CI gate all see the same rule ids.",
365
375
  inputSchema: {
366
376
  type: "object",
367
377
  properties: {
@@ -371,7 +381,7 @@ var checkTool = {
371
381
  },
372
382
  configJson: {
373
383
  type: "string",
374
- description: "Optional JSON-serialized seogeo Config. When absent the evaluator uses Config::default(), matching a cold CLI run."
384
+ description: "Optional JSON-serialized Aexeo config. When absent the evaluator uses Config::default(), matching a cold CLI run."
375
385
  }
376
386
  },
377
387
  required: ["documents"]
@@ -398,8 +408,8 @@ var checkTool = {
398
408
  }
399
409
  };
400
410
  var indexNowSubmitTool = {
401
- name: "seogeo.indexnow.submit",
402
- description: "Submit a list of URLs to the IndexNow freshness endpoint. Mirrors the seogeo-cli `indexnow submit` contract: the IndexNow protocol requires every submitted URL to belong to the configured site host. URLs that do not match the host are returned in `rejected` and never sent over the wire.",
411
+ name: "aexeo.indexnow.submit",
412
+ description: "Submit a list of URLs to the IndexNow freshness endpoint. Mirrors the aexeo-cli `indexnow submit` contract: the IndexNow protocol requires every submitted URL to belong to the configured site host. URLs that do not match the host are returned in `rejected` and never sent over the wire.",
403
413
  inputSchema: {
404
414
  type: "object",
405
415
  properties: {
@@ -431,7 +441,7 @@ function definePlugin(plugin) {
431
441
  }
432
442
  async function handleAdminRoute(input, ctx) {
433
443
  ctx.log?.info?.(
434
- `seogeo route: type=${input.input?.type} page=${input.input?.page ?? ""}`
444
+ `aexeo route: type=${input.input?.type} page=${input.input?.page ?? ""}`
435
445
  );
436
446
  const dispatch = { body: input.input, kv: ctx.kv, ctx };
437
447
  if (dispatch.body.type === "page_load") {
@@ -470,7 +480,7 @@ async function handlePageLoad(ctx, page) {
470
480
  if (normalized === "findings") {
471
481
  return renderFindingsPage(ctx);
472
482
  }
473
- if (normalized === "widget:seogeo-score") {
483
+ if (normalized === "widget:aexeo-score") {
474
484
  return renderScoreWidget(ctx);
475
485
  }
476
486
  if (normalized === "document") {
@@ -500,7 +510,7 @@ async function handleRefresh(ctx) {
500
510
  return {
501
511
  ok: false,
502
512
  reason: "config_missing",
503
- detail: "sidecar not configured \u2014 open the seogeo Setup page and enter your evaluator URL and token"
513
+ detail: "sidecar not configured \u2014 open the Aexeo Setup page and enter your evaluator URL and token"
504
514
  };
505
515
  }
506
516
  return evaluateViaSidecar(
@@ -759,10 +769,10 @@ function documentFindingsTable(findings) {
759
769
  async function renderSetupPage(ctx, options = {}) {
760
770
  const existing = await readSidecarConfig(ctx.kv);
761
771
  const blocks = [
762
- { type: "header", text: "seogeo setup" },
772
+ { type: "header", text: "Aexeo setup" },
763
773
  {
764
774
  type: "context",
765
- text: "Paste the URL and auth token of your deployed seogeo-crawl-worker. These are stored in plugin KV and read at runtime \u2014 no rebuild required after a change. Rotate the token here whenever you redeploy the sidecar with a new secret."
775
+ text: "Paste the URL and auth token of your deployed aexeo-crawl-worker. These are stored in plugin KV and read at runtime \u2014 no rebuild required after a change. Rotate the token here whenever you redeploy the sidecar with a new secret."
766
776
  }
767
777
  ];
768
778
  if (options.bannerError !== void 0) {
@@ -786,7 +796,7 @@ async function renderSetupPage(ctx, options = {}) {
786
796
  type: "text_input",
787
797
  action_id: "evaluator_url",
788
798
  label: "Sidecar URL",
789
- placeholder: "https://seogeo-crawl-worker.<subdomain>.workers.dev",
799
+ placeholder: "https://aexeo-crawl-worker.<subdomain>.workers.dev",
790
800
  initial_value: options.initialUrl ?? existing?.url ?? ""
791
801
  },
792
802
  {
package/dist/sandbox.d.ts CHANGED
@@ -18,8 +18,8 @@ export interface SandboxedPluginDescriptor {
18
18
  adminWidgets?: readonly PluginAdminWidget[];
19
19
  allowedHosts?: readonly string[];
20
20
  }
21
- export interface SeogeoSandboxedOptions {
21
+ export interface AexeoSandboxedOptions {
22
22
  evaluatorHost?: string;
23
23
  }
24
- export declare function seogeoPluginSandboxed(options?: SeogeoSandboxedOptions): SandboxedPluginDescriptor;
24
+ export declare function aexeoPluginSandboxed(options?: AexeoSandboxedOptions): SandboxedPluginDescriptor;
25
25
  //# sourceMappingURL=sandbox.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IAGnB,MAAM,EAAE,UAAU,CAAC;IACnB,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,UAAU,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAGxC,YAAY,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC5C,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAeD,MAAM,WAAW,sBAAsB;IAgBrC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,sBAA2B,GACnC,yBAAyB,CAwB3B"}
1
+ {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IAGnB,MAAM,EAAE,UAAU,CAAC;IACnB,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,UAAU,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAGxC,YAAY,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC5C,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAeD,MAAM,WAAW,qBAAqB;IAgBpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,qBAA0B,GAClC,yBAAyB,CAwB3B"}
@@ -0,0 +1,17 @@
1
+ import type { Finding } from "./types.js";
2
+ export interface Suppression {
3
+ routePattern?: string;
4
+ ruleIds?: readonly string[];
5
+ collections?: readonly string[];
6
+ statuses?: readonly string[];
7
+ }
8
+ export interface SuppressionContext {
9
+ route: string;
10
+ collection?: string;
11
+ status?: string;
12
+ }
13
+ export interface SuppressionFilter {
14
+ apply(context: SuppressionContext, findings: readonly Finding[]): Finding[];
15
+ }
16
+ export declare function compileSuppressions(rules: readonly Suppression[] | undefined): SuppressionFilter;
17
+ //# sourceMappingURL=suppressions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suppressions.d.ts","sourceRoot":"","sources":["../src/suppressions.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAsB1C,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B;AAOD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID,MAAM,WAAW,iBAAiB;IAChC,KAAK,CACH,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,EAAE,SAAS,OAAO,EAAE,GAC3B,OAAO,EAAE,CAAC;CACd;AASD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,SAAS,WAAW,EAAE,GAAG,SAAS,GACxC,iBAAiB,CAyEnB"}
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const PACKAGE_VERSION = "0.3.0";
1
+ export declare const PACKAGE_VERSION = "0.5.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@aeptus/aexeo-emdash",
3
- "version": "0.3.0",
4
- "description": "Aexeo seogeo SEO/GEO content evaluator as an emdash plugin",
3
+ "version": "0.5.0",
4
+ "description": "Aexeo SEO/GEO content evaluator as an emdash plugin",
5
5
  "license": "MIT",
6
- "author": "Aexeo",
6
+ "author": "Aeptus",
7
7
  "homepage": "https://github.com/schiste/Aexeo/tree/main/packages/aexeo-emdash",
8
8
  "repository": {
9
9
  "type": "git",
Binary file