@aeptus/aexeo-emdash 0.3.0 → 0.4.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,54 @@ and the project follows [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.4.0] - 2026-05-01
10
+
11
+ Adds upstreamed support for editor-workflow finding suppressions.
12
+ Removes the last reason for consumer sites to maintain a local
13
+ patch over the published plugin.
14
+
15
+ **Compatibility:** verified against emdash `0.7.0` and `0.8.0`.
16
+
17
+ ### Added
18
+
19
+ - **`seogeoPlugin({ suppressions })` option.** Each rule silences
20
+ findings matching the route pattern (glob) AND/OR a rule-id set.
21
+ Applied before findings are persisted to KV — suppressed findings
22
+ never reach the dashboard, /findings, or the per-document panel.
23
+
24
+ ```ts
25
+ seogeoPlugin({
26
+ suppressions: [
27
+ { routePattern: "/privacy", ruleIds: ["RULE001"] },
28
+ { routePattern: "/fr-fr/**", ruleIds: ["RULE002"] },
29
+ { ruleIds: ["RULE999"] }, // applies to every route
30
+ ],
31
+ })
32
+ ```
33
+
34
+ Glob: `*` matches non-`/` chars, `**` matches across `/`, `?`
35
+ matches one non-`/` char. Patterns are anchored. Empty rules
36
+ (`{}` with neither field) are rejected at plugin construction
37
+ with a clear error — that's a kill-switch for all findings
38
+ everywhere and almost certainly an editor mistake.
39
+
40
+ - **`Suppression` type** re-exported from the package root for hosts
41
+ that want to type their config:
42
+
43
+ ```ts
44
+ import type { Suppression } from "@aeptus/aexeo-emdash";
45
+ ```
46
+
47
+ ### Notes for hosts upgrading from 0.3.x
48
+
49
+ - No automatic behavior change. `suppressions` defaults to undefined
50
+ and the filter is a no-op until the host opts in.
51
+ - Suppressions are plugin-only by design. The CLI `check` continues
52
+ to surface every finding; if you want to silence findings at the
53
+ CLI layer, use `seogeo.toml`'s `[ignore]` block. The two surfaces
54
+ serve different audiences: the plugin is editorial workflow, the
55
+ CLI is build-gating.
56
+
9
57
  ## [0.3.0] - 2026-05-01
10
58
 
11
59
  Adds an LLM-assisted authoring flow for the truth manifest
@@ -218,7 +266,8 @@ First public release.
218
266
  `astro.config.mjs` for the WASM import to resolve to a
219
267
  precompiled `WebAssembly.Module`. Not optional.
220
268
 
221
- [Unreleased]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.3.0...HEAD
269
+ [Unreleased]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.4.0...HEAD
270
+ [0.4.0]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.3.0...aexeo-emdash-v0.4.0
222
271
  [0.3.0]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.2.0...aexeo-emdash-v0.3.0
223
272
  [0.2.0]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.2...aexeo-emdash-v0.2.0
224
273
  [0.1.2]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.1...aexeo-emdash-v0.1.2
@@ -1,3 +1,4 @@
1
+ import type { Suppression } from "./suppressions.js";
1
2
  export interface ConfiguredPluginOptions {
2
3
  /**
3
4
  * emdash collections the Refresh button sweeps. Comes through from
@@ -6,6 +7,13 @@ export interface ConfiguredPluginOptions {
6
7
  * (`["posts", "pages"]`).
7
8
  */
8
9
  collections?: readonly string[];
10
+ /**
11
+ * Editor-workflow suppressions. See `Suppression` in suppressions.ts
12
+ * for the rule shape and matching semantics. Compiled once at plugin
13
+ * construction; the resulting filter is captured in the closures
14
+ * attached to the route handlers and the afterSave hook.
15
+ */
16
+ suppressions?: readonly Suppression[];
9
17
  }
10
18
  export declare function createPlugin(options?: ConfiguredPluginOptions): unknown;
11
19
  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;AAoDrD,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEhC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CACvC;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CA+F3E;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,16 @@ 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(document.route, pageFindings);
92
93
  await kv.set(findingsKey(document.route), {
93
94
  route: document.route,
94
- findings: pageFindings
95
+ findings: filtered
95
96
  });
96
97
  }
97
98
  var DEFAULT_COLLECTIONS = ["posts", "pages"];
98
99
  async function evaluateAndPersistAll(ctx, options) {
99
100
  const collections = options.collections ?? DEFAULT_COLLECTIONS;
101
+ const suppressionFilter = options.suppressionFilter;
100
102
  const { kv, log } = ctx;
101
103
  const summary = {
102
104
  documentsScanned: 0,
@@ -154,11 +156,14 @@ async function evaluateAndPersistAll(ctx, options) {
154
156
  list.push(finding);
155
157
  findingsByRoute.set(bucket, list);
156
158
  }
159
+ let totalAfterSuppression = 0;
157
160
  for (const [route, findings] of findingsByRoute) {
158
- await kv.set(findingsKey(route), { route, findings });
161
+ const filtered = suppressionFilter === void 0 ? findings : suppressionFilter.apply(route, findings);
162
+ await kv.set(findingsKey(route), { route, findings: filtered });
159
163
  summary.routesUpdated += 1;
164
+ totalAfterSuppression += filtered.length;
160
165
  }
161
- summary.totalFindings = result.findings.length;
166
+ summary.totalFindings = totalAfterSuppression;
162
167
  return summary;
163
168
  }
164
169
  function findingsKey(route) {
@@ -446,7 +451,8 @@ async function handleDataRoute(ctx, options) {
446
451
  if (options.refresh) {
447
452
  summary = await evaluateAndPersistAll(ctx, {
448
453
  collections: options.collections,
449
- evaluator: options.evaluator
454
+ evaluator: options.evaluator,
455
+ ...options.suppressionFilter === void 0 ? {} : { suppressionFilter: options.suppressionFilter }
450
456
  });
451
457
  await persistManifestStateFindings(ctx);
452
458
  }
@@ -724,8 +730,50 @@ async function handleSave(ctx, body) {
724
730
  return { data: { saved: true } };
725
731
  }
726
732
 
733
+ // src/suppressions.ts
734
+ var NOOP_FILTER = {
735
+ apply: (_route, findings) => [...findings]
736
+ };
737
+ function compileSuppressions(rules) {
738
+ if (rules === void 0 || rules.length === 0) {
739
+ return NOOP_FILTER;
740
+ }
741
+ rules.forEach((rule, index) => {
742
+ const hasRoute = rule.routePattern !== void 0 && rule.routePattern.length > 0;
743
+ const hasRuleIds = rule.ruleIds !== void 0 && rule.ruleIds.length > 0;
744
+ if (!hasRoute && !hasRuleIds) {
745
+ throw new Error(
746
+ `seogeoPlugin: suppressions[${index}] is empty \u2014 set routePattern, ruleIds, or both`
747
+ );
748
+ }
749
+ });
750
+ const compiled = rules.map((rule) => ({
751
+ matchRoute: rule.routePattern === void 0 ? matchAlways : globToMatcher(rule.routePattern),
752
+ ruleIds: rule.ruleIds === void 0 || rule.ruleIds.length === 0 ? null : new Set(rule.ruleIds)
753
+ }));
754
+ return {
755
+ apply(route, findings) {
756
+ return findings.filter((finding) => !compiled.some((rule) => {
757
+ if (!rule.matchRoute(route)) return false;
758
+ if (rule.ruleIds !== null && !rule.ruleIds.has(finding.rule_id)) {
759
+ return false;
760
+ }
761
+ return true;
762
+ }));
763
+ }
764
+ };
765
+ }
766
+ var matchAlways = () => true;
767
+ function globToMatcher(pattern) {
768
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
769
+ const DOUBLE_STAR_TOKEN = "";
770
+ let body = escaped.replace(/\*\*/g, DOUBLE_STAR_TOKEN).replace(/\*/g, "[^/]*").replace(new RegExp(DOUBLE_STAR_TOKEN, "g"), ".*").replace(/\?/g, "[^/]");
771
+ const re = new RegExp(`^${body}$`);
772
+ return (route) => re.test(route);
773
+ }
774
+
727
775
  // src/version.ts
728
- var PACKAGE_VERSION = "0.3.0";
776
+ var PACKAGE_VERSION = "0.4.0";
729
777
 
730
778
  // src/configured.ts
731
779
  import { definePlugin } from "emdash";
@@ -748,6 +796,7 @@ var inProcessEvaluator = async (documents) => {
748
796
  };
749
797
  function createPlugin(options = {}) {
750
798
  const collections = options.collections === void 0 ? DEFAULT_COLLECTIONS : [...options.collections];
799
+ const suppressionFilter = compileSuppressions(options.suppressions);
751
800
  return definePlugin({
752
801
  id: "aexeo-seogeo",
753
802
  version: PACKAGE_VERSION,
@@ -756,7 +805,12 @@ function createPlugin(options = {}) {
756
805
  // afterSave processes one saved document at a time — collections
757
806
  // list isn't needed here. The hook always runs regardless of
758
807
  // which collection the document came from.
759
- "content:afterSave": (event, ctx) => handleAfterSaveConfigured(event, ctx, inProcessEvaluator)
808
+ "content:afterSave": (event, ctx) => handleAfterSaveConfigured(
809
+ event,
810
+ ctx,
811
+ inProcessEvaluator,
812
+ suppressionFilter
813
+ )
760
814
  },
761
815
  routes: {
762
816
  // The Refresh sweep (handleAdminRoute → handleRefresh) is the
@@ -776,14 +830,16 @@ function createPlugin(options = {}) {
776
830
  handler: async (ctx) => (await handleDataRoute(ctx, {
777
831
  collections,
778
832
  evaluator: inProcessEvaluator,
779
- refresh: false
833
+ refresh: false,
834
+ suppressionFilter
780
835
  })).payload
781
836
  },
782
837
  refresh: {
783
838
  handler: (ctx) => handleDataRoute(ctx, {
784
839
  collections,
785
840
  evaluator: inProcessEvaluator,
786
- refresh: true
841
+ refresh: true,
842
+ suppressionFilter
787
843
  })
788
844
  },
789
845
  // /facts route serves the truth-manifest authoring UI. It
@@ -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
@@ -40,6 +40,36 @@ export interface SeogeoPluginOptions {
40
40
  * add collections later.
41
41
  */
42
42
  collections?: readonly string[];
43
+ /**
44
+ * Editor-workflow suppressions. Each rule silences findings that
45
+ * match the route pattern (glob) AND/OR are in the rule-id set.
46
+ * Applied before findings are persisted to KV — suppressed findings
47
+ * never reach the dashboard, /findings, or the per-document panel.
48
+ *
49
+ * seogeoPlugin({
50
+ * suppressions: [
51
+ * { routePattern: "/privacy", ruleIds: ["RULE001"] },
52
+ * { routePattern: "/fr-fr/**", ruleIds: ["RULE002"] },
53
+ * { ruleIds: ["RULE999"] }, // applies to every route
54
+ * ],
55
+ * })
56
+ *
57
+ * Glob syntax: `*` matches any chars except `/`; `**` matches across
58
+ * `/`; `?` matches one char except `/`. Patterns are anchored.
59
+ *
60
+ * Empty rules (`{}` with neither field set) are rejected at plugin
61
+ * construction — that's a kill-switch for all findings everywhere
62
+ * and almost certainly an editor mistake.
63
+ *
64
+ * Suppressions are plugin-only by design. The CLI's `check` is the
65
+ * canonical strict audit; if you want to silence findings at the
66
+ * CLI layer, use `seogeo.toml`'s `[ignore]` block instead. The two
67
+ * surfaces serve different audiences: the plugin is editorial
68
+ * workflow, the CLI is build-gating.
69
+ */
70
+ suppressions?: readonly Suppression[];
43
71
  }
72
+ export type { Suppression } from "./suppressions.js";
73
+ import type { Suppression } from "./suppressions.js";
44
74
  export declare function seogeoPlugin(options?: SeogeoPluginOptions): ConfiguredPluginDescriptor;
45
75
  //# 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,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;IAEhC;;;;;;;;;;;;;;;;;;;;;;;;;;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,YAAY,CAC1B,OAAO,GAAE,mBAAwB,GAChC,0BAA0B,CAkD5B"}
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ function buildAllowedHosts(evaluatorHost) {
23
23
  }
24
24
 
25
25
  // src/version.ts
26
- var PACKAGE_VERSION = "0.3.0";
26
+ var PACKAGE_VERSION = "0.4.0";
27
27
 
28
28
  // src/sandbox.ts
29
29
  function seogeoPluginSandboxed(options = {}) {
@@ -75,7 +75,8 @@ 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.suppressions === void 0 ? {} : { suppressions: options.suppressions.map((rule) => ({ ...rule })) }
79
80
  },
80
81
  capabilities: [
81
82
  "read:content",
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,
@@ -146,11 +147,14 @@ 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 filtered = suppressionFilter === void 0 ? findings : suppressionFilter.apply(route, findings);
153
+ await kv.set(findingsKey(route), { route, findings: filtered });
151
154
  summary.routesUpdated += 1;
155
+ totalAfterSuppression += filtered.length;
152
156
  }
153
- summary.totalFindings = result.findings.length;
157
+ summary.totalFindings = totalAfterSuppression;
154
158
  return summary;
155
159
  }
156
160
  function findingsKey(route) {
@@ -0,0 +1,10 @@
1
+ import type { Finding } from "./types.js";
2
+ export interface Suppression {
3
+ routePattern?: string;
4
+ ruleIds?: readonly string[];
5
+ }
6
+ export interface SuppressionFilter {
7
+ apply(route: string, findings: readonly Finding[]): Finding[];
8
+ }
9
+ export declare function compileSuppressions(rules: readonly Suppression[] | undefined): SuppressionFilter;
10
+ //# sourceMappingURL=suppressions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suppressions.d.ts","sourceRoot":"","sources":["../src/suppressions.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAkB1C,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAID,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC;CAC/D;AASD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,SAAS,WAAW,EAAE,GAAG,SAAS,GACxC,iBAAiB,CAoCnB"}
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.4.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aeptus/aexeo-emdash",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Aexeo seogeo SEO/GEO content evaluator as an emdash plugin",
5
5
  "license": "MIT",
6
6
  "author": "Aexeo",