@aeptus/aexeo-emdash 0.8.18 → 0.9.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.
Files changed (84) hide show
  1. package/CHANGELOG.md +196 -4
  2. package/INSTALL.md +126 -8
  3. package/LICENSE +0 -0
  4. package/README.md +12 -5
  5. package/dist/adapter.d.ts +30 -2
  6. package/dist/adapter.d.ts.map +1 -1
  7. package/dist/admin/Absorbability.d.ts +0 -0
  8. package/dist/admin/Absorbability.d.ts.map +0 -0
  9. package/dist/admin/Accessibility.d.ts +0 -0
  10. package/dist/admin/Accessibility.d.ts.map +0 -0
  11. package/dist/admin/Citability.d.ts +0 -0
  12. package/dist/admin/Citability.d.ts.map +0 -0
  13. package/dist/admin/EntityLegitimacy.d.ts +0 -0
  14. package/dist/admin/EntityLegitimacy.d.ts.map +0 -0
  15. package/dist/admin/EntityPresence.d.ts +0 -0
  16. package/dist/admin/EntityPresence.d.ts.map +0 -0
  17. package/dist/admin/Facts.d.ts +0 -0
  18. package/dist/admin/Facts.d.ts.map +0 -0
  19. package/dist/admin/Findings.d.ts +0 -0
  20. package/dist/admin/Findings.d.ts.map +0 -0
  21. package/dist/admin/PillarView.d.ts +0 -0
  22. package/dist/admin/PillarView.d.ts.map +0 -0
  23. package/dist/admin/Retrievability.d.ts +0 -0
  24. package/dist/admin/Retrievability.d.ts.map +0 -0
  25. package/dist/admin.d.ts +0 -0
  26. package/dist/admin.d.ts.map +1 -1
  27. package/dist/admin.js +2 -2
  28. package/dist/configured.d.ts +6 -0
  29. package/dist/configured.d.ts.map +1 -1
  30. package/dist/configured.js +817 -334
  31. package/dist/content-evaluator.d.ts +14 -0
  32. package/dist/content-evaluator.d.ts.map +1 -0
  33. package/dist/content.d.ts +51 -0
  34. package/dist/content.d.ts.map +1 -0
  35. package/dist/content.js +365 -0
  36. package/dist/data-route.d.ts +3 -0
  37. package/dist/data-route.d.ts.map +1 -1
  38. package/dist/evaluator-sandbox.d.ts +1 -1
  39. package/dist/evaluator-sandbox.d.ts.map +1 -1
  40. package/dist/evaluator.d.ts +1 -1
  41. package/dist/evaluator.d.ts.map +1 -1
  42. package/dist/facts-route.d.ts +0 -0
  43. package/dist/facts-route.d.ts.map +0 -0
  44. package/dist/findings-view.d.ts +141 -0
  45. package/dist/findings-view.d.ts.map +1 -0
  46. package/dist/index.d.ts +15 -1
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +111 -5
  49. package/dist/inventory.d.ts +51 -0
  50. package/dist/inventory.d.ts.map +1 -0
  51. package/dist/plugin.d.ts +51 -4
  52. package/dist/plugin.d.ts.map +1 -1
  53. package/dist/presence-check.d.ts +1 -0
  54. package/dist/presence-check.d.ts.map +1 -1
  55. package/dist/presence-route.d.ts +0 -0
  56. package/dist/presence-route.d.ts.map +0 -0
  57. package/dist/sandbox-entry.d.ts +4 -23
  58. package/dist/sandbox-entry.d.ts.map +1 -1
  59. package/dist/sandbox-entry.js +738 -396
  60. package/dist/sandbox.d.ts +2 -0
  61. package/dist/sandbox.d.ts.map +1 -1
  62. package/dist/sidecar-url.d.ts +18 -0
  63. package/dist/sidecar-url.d.ts.map +1 -0
  64. package/dist/sidecar.d.ts +7 -1
  65. package/dist/sidecar.d.ts.map +1 -1
  66. package/dist/suppressions.d.ts +0 -0
  67. package/dist/suppressions.d.ts.map +0 -0
  68. package/dist/types.d.ts +11 -4
  69. package/dist/types.d.ts.map +1 -1
  70. package/dist/version.d.ts +1 -1
  71. package/dist/version.d.ts.map +1 -1
  72. package/dist/wasm-init.d.ts +4 -0
  73. package/dist/wasm-init.d.ts.map +1 -1
  74. package/package.json +21 -8
  75. package/wasm/aexeo_emdash_bridge.d.ts +11 -0
  76. package/wasm/aexeo_emdash_bridge.js +1 -1
  77. package/wasm/aexeo_emdash_bridge_bg.d.ts +11 -0
  78. package/wasm/aexeo_emdash_bridge_bg.js +68 -0
  79. package/wasm/aexeo_emdash_bridge_bg.wasm +0 -0
  80. package/wasm/aexeo_emdash_bridge_bg.wasm.d.ts +2 -0
  81. package/dist/indexnow.d.ts +0 -15
  82. package/dist/indexnow.d.ts.map +0 -1
  83. package/dist/mcp.d.ts +0 -28
  84. package/dist/mcp.d.ts.map +0 -1
package/CHANGELOG.md CHANGED
@@ -6,14 +6,167 @@ and the project follows [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
- ## [0.8.18] - 2026-09-27
9
+ ## [0.9.0] - 2026-11-10
10
10
 
11
- Updates the EmDash integration to the current plugin and content APIs.
11
+ ### Added
12
+
13
+ - Portable content presets with per-collection selection, typed body capabilities and explicit incomplete results.
14
+ - Ordered mappings for multiple content fields and custom blocks, including nested repeats and heading levels, in configured and sandboxed modes.
15
+ - Conservative CMS link inventory export, source locations and aggregate content coverage telemetry.
16
+ - The configured dashboard reports evaluator failures without breaking the editor and tolerates older findings without secondary layers.
17
+
18
+ ### Compatibility
19
+
20
+ - Supports EmDash `^1.2.0`. Deploy the matching sidecar when using sandboxed mode.
21
+ - The intelligence widget reports facts and blockers instead of the removed composite score.
22
+ - Existing site and CLI checks remain separate from optional content presets.
23
+
24
+ ### Changed
25
+
26
+ - **The dashboard widget no longer shows a composite score.** The "Overall"
27
+ stat and the "Site score is N — below the 60 quality threshold" banner are
28
+ gone; blockers remain the call to action. `SiteIntelligenceScore` loses
29
+ `overall_score`, `answer_pack_score` and `elapsed_us` and gains
30
+ `answer_pack`, and `citation_readiness_score` and `truth_consistency_score`
31
+ may now be `null`, shown as "unknown", when nothing was measured.
32
+ - **Entity presence resolves the newest Common Crawl index at runtime** from
33
+ `collinfo.json` instead of a pinned crawl, so a site first crawled after a
34
+ plugin release is no longer reported `not_found`. Failing to resolve the
35
+ index reports `unreachable`.
36
+ - Support EmDash `^1.2.0` and run the plugin-host integration coverage against
37
+ EmDash `1.2.0`.
38
+
39
+ ### Added
40
+
41
+ - **The sandboxed plugin warns when its sidecar is on another version.** The
42
+ Worker reports its version in the `x-aexeo-version` response header, and the
43
+ plugin logs a warning when it differs, since a Worker left behind on an upgrade
44
+ keeps evaluating with older rules.
45
+
46
+ ### Security
47
+
48
+ - **EVAL_TOKEN never falls back to plaintext.** When the host cannot encrypt
49
+ the token (usually `EMDASH_ENCRYPTION_KEY` is unset), the Setup page now shows
50
+ an error and changes nothing, where it used to store the token in plaintext KV
51
+ with only a log line. **Behaviour change:** on such a host, saving the Setup
52
+ form fails until the key is set, even for a URL-only change. A token already
53
+ in KV keeps working, and the Setup page says it is unencrypted.
54
+ - **A rotated token is no longer ignored.** Reads preferred settings, so a token
55
+ that fell back to KV during a rotation lost to the older one in settings. A KV
56
+ token is always the newer of the two, and is now read first.
57
+ - **The Setup form rejects trailing-dot hosts** such as `https://localhost./`,
58
+ which passed the loopback check.
59
+
60
+ ### Changed
61
+
62
+ - **`aexeo-crawl-worker` upgrade notes.** The Worker refuses every request when
63
+ `EVAL_TOKEN` is shorter than 32 characters, so rotate a shorter token before
64
+ redeploying. It answers `413` above 8 MiB, the limit of emdash's sandbox
65
+ bridge. Malformed bodies and initialization failures now return JSON errors, and
66
+ token comparison is constant-time. It is built with Wrangler 4 and a newer
67
+ compatibility date.
68
+ - **Editors can read findings.** The configured plugin's `admin` and `data`
69
+ routes declare `plugins:read`; emdash otherwise requires `plugins:manage`.
70
+ Refresh, facts and presence stay admin-only, and the `admin` route refuses
71
+ `refresh_findings` for non-admins. The sandboxed entry stays admin-only because
72
+ its `admin` route also saves the Setup form.
73
+ - **The configured plugin declares its network use**: `network:request` and the
74
+ presence sources' hosts, which it was using while declaring only `content:read`.
75
+
76
+ ### Fixed
77
+
78
+ - **Concurrent writes no longer lose newer data.** A save older than the stored
79
+ revision is ignored, a slow evaluation does not overwrite the findings of a
80
+ later save or delete, and a Refresh does not overwrite a save that landed during
81
+ the sweep or remove a document created during it.
82
+ - **Deleted content leaves the findings immediately**, through a
83
+ `content:afterDelete` hook, including when it is only trashed. Restoring an
84
+ entry brings it back at its next save or Refresh.
85
+ - **Legacy `document:` entries are removed** by a Refresh that does not see their
86
+ route. They record no collection, so they were previously never removed.
87
+ - **The sandboxed score widget reports the score as unavailable** instead of a
88
+ score of 0, which read as a measured result.
89
+ - **Entity presence no longer reports false results.** Common Crawl searches the
90
+ whole domain, where an exact-URL match reported a site absent when the crawl
91
+ skipped only its home page. Wikipedia and Wikidata records must name the entity
92
+ or a declared alias, ignoring legal forms; a substring used to count, so
93
+ "Pineapple" matched "Apple".
94
+ - **Saving a document no longer reports false site-graph findings or erases
95
+ real ones.** A save evaluates one document as a one-page site, so every
96
+ internal link was reported as broken (`LNK001`) and the page as an orphan
97
+ (`LNK002`), and those results replaced the route's stored findings until the
98
+ next Refresh. Rules that depend on other documents (`LNK001`-`LNK004`,
99
+ `SEO009`, `SEO012`, `SEO016`, `SEO017`, `CNT005`) are now computed by Refresh
100
+ only: a save keeps the route's stored findings for them, so a link fixed or
101
+ added in that save is reflected at the next Refresh.
102
+
103
+ ## [0.8.19] - 2026-09-29
104
+
105
+ Updates the EmDash integration to the current plugin and content APIs, and
106
+ closes three security findings. Everything since 0.8.18 ships in this one
107
+ version, since 0.8.19 had never been published.
12
108
 
13
109
  **Compatibility target:** EmDash `>=0.41.0 <0.42.0`.
14
110
 
111
+ ### Security
112
+
113
+ - **EVAL_TOKEN is stored through the host's encrypted plugin settings.**
114
+ The sidecar bearer token was written as plaintext into the plugin's KV
115
+ namespace. EmDash provides `PluginContext.settings` with host-side
116
+ encryption for fields declared `type: "secret"` in a plugin's
117
+ `settingsSchema`, which is exactly this case; the descriptor now declares
118
+ that schema and the token is written there. Installs that predate the
119
+ change have their plaintext KV copy deleted on the next write, so the
120
+ credential is not sitting in the database twice. Hosts without
121
+ `EMDASH_ENCRYPTION_KEY` configured fall back to the previous KV path with
122
+ a warning that names the missing environment variable and never the
123
+ token, so the Setup page keeps working instead of failing closed.
124
+ - **Sidecar URL validation is allowlist-backed and HTTPS-only.**
125
+ `handleSetupSubmit` accepted `http:`, which sent the bearer token in
126
+ cleartext, and rejected only `localhost` and `127.*` — leaving
127
+ `0.0.0.0`, `[::1]`, RFC1918, CGNAT, and the `169.254.169.254` cloud
128
+ metadata address reachable. It now requires HTTPS, rejects the private,
129
+ loopback, link-local, multicast, and IPv4-mapped-IPv6 ranges, and
130
+ compares the submitted host against the `allowedHosts` list the plugin
131
+ already computes at build time, so the form cannot store a URL the
132
+ plugin's own allowlist would reject later.
133
+ - **Unauthenticated crawl-artifact routes removed from
134
+ `packages/aexeo-crawl-worker`.** `GET /findings/latest` returned the full
135
+ body of the customer's `aexeo-cli crawl` artifact and
136
+ `GET /findings/list` returned R2 key names, timestamps, and sizes.
137
+ Neither was behind the bearer-token check that guarded `/evaluate`, and
138
+ neither had any caller in this repository. CORS is not access control, so
139
+ anyone who learned the worker hostname could read the whole crawl
140
+ artifact. Both routes, the R2 binding, and the `examples/github-actions.yml`
141
+ R2 upload workflow that existed only to feed them are gone.
142
+
143
+ ### Removed
144
+
145
+ - **`src/mcp.ts` and `src/indexnow.ts`.** Both were unreachable. The host
146
+ reads `PluginDefinition.mcp` as a `Record` of tool definitions keyed by
147
+ name; the code attached an `mcpTools` array with a different container and
148
+ a different per-entry shape, and `configured.ts` never attached it at
149
+ all. No MCP tool this package offered could ever have been invoked.
150
+
15
151
  ### Fixed
16
152
 
153
+ - **Suppressions ignored by the Block Kit Refresh button.** In configured
154
+ mode the /findings "Refresh" button called the sweep without the
155
+ compiled `suppressionFilter`, so it re-persisted findings the editor
156
+ had explicitly silenced. The React `/refresh` route always applied
157
+ them, so the two surfaces disagreed and the documented guarantee
158
+ ("suppressed findings never reach the dashboard, /findings, or the
159
+ per-document panel") did not hold on the Block Kit path.
160
+ Root cause: `configured.ts` and `sandbox-entry.ts` each carried their
161
+ own near-verbatim copy of the interaction normalizer, the findings
162
+ page, the score widget, the document panel, and the refresh handler.
163
+ The copies had already drifted — this missing argument was one of the
164
+ drifts. Fix: the whole block now lives once in `src/findings-view.ts`,
165
+ and `RefreshOptions.suppressionFilter` is a **required** property, so a
166
+ caller cannot silently omit it again.
167
+ - **`/evaluate` no longer 500s when `EVAL_TOKEN` is unset.** The check read
168
+ `env.EVAL_TOKEN.length` without a guard, so a worker deployed without the
169
+ secret threw a `TypeError` on every request instead of rejecting it.
17
170
  - **Sandbox access declaration:** replace obsolete capability strings with
18
171
  `content:read` and conditional `network:request`, scoped to the configured
19
172
  evaluator host. Remove unused schema, artifact, KV, and IndexNow host grants.
@@ -32,10 +185,49 @@ Updates the EmDash integration to the current plugin and content APIs.
32
185
 
33
186
  ### Changed
34
187
 
188
+ - Extract the shared Block Kit view layer (`normalizeInteraction`,
189
+ `BlockInteraction`, `BlockResponse`, the findings page, the score
190
+ widget, the document panel, and the refresh sweep) out of
191
+ `configured.ts` and `sandbox-entry.ts` into `src/findings-view.ts`.
192
+ Behaviour is unchanged apart from the suppression fix above; the two
193
+ entrypoints keep their own dispatch and their own copy of the three
194
+ strings that legitimately differ (empty-state copy, score-widget
195
+ empty copy, hard-refresh banner variant), passed in through
196
+ `AdminViewTuning`. `BlockInteraction` and `BlockResponse` are
197
+ re-exported from `sandbox-entry.ts`, so the `./sandbox` entry's
198
+ public type surface is unchanged.
35
199
  - Bump the EmDash peer and development ranges to `0.41.x`; update the README
36
200
  and install guide to name the supported range and sandbox save behavior.
37
- - Add Vitest coverage using EmDash's official plugin runtime test host for
38
- sandbox hooks, blocks extraction, resolved URLs, and translation alternates.
201
+ - Add regression tests for `normalizeInteraction` — the ten edge cases
202
+ the 0.8.17 changelog claimed to have verified but never did — plus
203
+ entrypoint parity tests that drive both admin routes with the same
204
+ malformed bodies, and a test that the configured Block Kit refresh and
205
+ the React `/refresh` route agree on suppressed findings. Vitest coverage
206
+ now runs against EmDash's official plugin runtime test host for sandbox
207
+ hooks, blocks extraction, resolved URLs, and translation alternates.
208
+ - Add `tsconfig.test.json` and a `typecheck:tests` script so `tests/`
209
+ and `vitest.config.ts` are typechecked. `tsconfig.json`'s `include`
210
+ covered `src/**` only, which meant the suite guarding the 0.8.17
211
+ production hotfix and the emdash test-host fixture plugin were both
212
+ unchecked. `npm run typecheck` now runs `typecheck:src` and
213
+ `typecheck:tests`.
214
+ - `packages/aexeo-crawl-worker` no longer commits its build artifacts. The
215
+ checked-in `aexeo_emdash_bridge_bg.wasm` and its glue were several
216
+ releases behind the bridge the plugin actually calls — missing
217
+ `generateFactsPrompt`, `validateFactsManifest`, the manifest argument on
218
+ `scoreIntelligence`, and the `__wbg_*` bindings — and nothing in the
219
+ repository could regenerate them. The hand-written `.d.ts` files had been
220
+ updated to match the stale binary, which is why it went unnoticed. The
221
+ binary is now produced by `npm run build:wasm`, chained into `dev` and
222
+ `deploy`, and only the two declaration files remain tracked. The package
223
+ also gains a README, which it did not have.
224
+
225
+ ## [0.8.18] - 2026-09-28
226
+
227
+ ### Added
228
+
229
+ - Include deterministic editorial-policy rules EDT001–EDT003 in the bundled Rust/WASM evaluator. Configured answer summaries, target questions, and evidence-marked claims can now be checked through the serialized Aexeo config.
230
+
39
231
 
40
232
  ## [0.8.17] - 2026-05-29
41
233
 
package/INSTALL.md CHANGED
@@ -5,6 +5,84 @@ 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.
7
7
 
8
+ ## Restricting content presets to specific collections
9
+
10
+ Content presets apply to all evaluated collections by default. Set
11
+ `contentPreset.collections` to select only the collections that use the preset;
12
+ existing site checks continue to run on all configured collections. Use
13
+ `byCollection` to choose an alternate preset for a selected collection.
14
+ For example:
15
+
16
+ ```js
17
+ aexeoPlugin({
18
+ contentPreset: {
19
+ name: "article",
20
+ collections: ["blog_posts", "glossary_terms"],
21
+ byCollection: { glossary_terms: "glossary" },
22
+ },
23
+ })
24
+ ```
25
+
26
+ ## Content coverage
27
+
28
+ Portable Text bodies provide text, headings, paragraphs and links to content
29
+ presets. A plain text body provides text only. EmDash custom blocks are
30
+ project-specific; their flattened text is available to the legacy site audit,
31
+ but content-preset gates that require body capabilities are reported as
32
+ `not_checked` until a faithful mapping is provided. The configured plugin
33
+ returns `content_preset_incomplete` for such a report, preserving its existing
34
+ fail-closed behavior. This avoids treating unobserved headings, paragraphs or
35
+ links as absent. An explicitly empty body
36
+ is distinct from an unavailable body. When an entry has several candidate
37
+ content fields, the default first-field projection remains unverified because
38
+ it cannot establish which fields appear in the rendered page.
39
+
40
+ For custom blocks in configured mode, declare the ordered rendered content
41
+ fields for each block type in the plugin configuration:
42
+
43
+ ```js
44
+ aexeoPlugin({
45
+ contentFieldsByCollection: { pages: ["introduction", "sections"] },
46
+ blockMappings: {
47
+ hero: [
48
+ { kind: "heading", field: "headline" },
49
+ { kind: "paragraph", field: "description", optional: true },
50
+ { kind: "link", field: "linkLabel", hrefField: "linkUrl", optional: true },
51
+ { kind: "richText", field: "body" },
52
+ ],
53
+ faq: [
54
+ { kind: "heading", field: "heading" },
55
+ { kind: "repeat", field: "faqs", fields: [
56
+ { kind: "heading", field: "question", level: 3 },
57
+ { kind: "paragraph", field: "answer" },
58
+ ] },
59
+ ],
60
+ },
61
+ })
62
+ ```
63
+
64
+ `contentFieldsByCollection` declares the body fields rendered by each selected
65
+ collection, in order. It is an explicit assertion of completeness, so include
66
+ all rendered fields; entries without any selected field remain unavailable.
67
+ In sandboxed mode, enter this object in `Content fields by collection (JSON)`.
68
+
69
+ A block mapping is the site's assertion that those fields cover the rendered content,
70
+ in order. `richText` accepts Portable Text arrays and preserves their headings
71
+ and links; `heading` defaults to h2 and accepts levels 1 through 6. `repeat`
72
+ maps each item in an array;
73
+ `optional: true` permits an absent field without inventing content. Every
74
+ encountered block type must be
75
+ mapped and every mapped field must have the expected type. If not, capabilities
76
+ remain unverified. These options apply to configured-mode saves and both
77
+ refresh routes. In sandboxed mode, enter the same JSON object in the plugin
78
+ settings field `Custom block mappings (JSON)`; it applies to save and Refresh.
79
+ Invalid or unavailable settings leave custom blocks unverified.
80
+
81
+ Adapters supplying an `EmdashDocument` can set `body_capabilities` to a subset
82
+ of `text`, `headings`, `paragraphs` and `links`. Omit it only when all four
83
+ are faithfully represented. These capability declarations describe input
84
+ coverage; they do not validate the content itself.
85
+
8
86
  ## Two install paths
9
87
 
10
88
  The package exports two factories:
@@ -31,8 +109,8 @@ when it's actually needed.
31
109
  (`@astrojs/cloudflare` + `@emdash-cms/cloudflare`'s `sandbox()`
32
110
  runner is fine — sandboxed plugins use Worker Loader, configured
33
111
  plugins don't, but both adapters import cleanly).
34
- - EmDash `>=0.41.0 <0.42.0` as a peer dependency. The integration
35
- coverage targets `0.41.0` with EmDash's official plugin test host.
112
+ - EmDash `^1.2.0` as a peer dependency. The integration coverage targets
113
+ `1.2.0` with EmDash's official plugin test host.
36
114
 
37
115
  ### Install
38
116
 
@@ -266,16 +344,25 @@ in this repo. Per-site deploy:
266
344
 
267
345
  ```bash
268
346
  cd aexeo-crawl-worker
269
- # Edit wrangler.toml: name, R2 bucket name, SITE_URL
347
+ npm ci
348
+ # Edit wrangler.toml: name, SITE_URL
270
349
  npx wrangler login
271
- npx wrangler r2 bucket create <bucket-name>
272
- npx wrangler deploy
350
+ npm run deploy # builds the WASM bridge, then deploys
273
351
  echo "$(openssl rand -hex 32)" | npx wrangler secret put EVAL_TOKEN
274
352
  ```
275
353
 
354
+ The Worker's WASM is generated, not committed — `npm run deploy` runs
355
+ `npm run build:wasm` first, which needs the Rust toolchain and
356
+ `wasm-bindgen`. See the template's README.
357
+
276
358
  Then in the admin UI, visit
277
359
  `/admin/plugins/aexeo-emdash/setup` and paste the deployed URL +
278
- the same token.
360
+ the same token. The URL must be `https`, must not point at a
361
+ loopback, private, or link-local address, and must be the host in
362
+ `evaluatorHost` above — the emdash sandbox rejects any other outbound
363
+ host, so the form checks it before storing. The token is written
364
+ through the host's encrypted plugin settings (`admin.settingsSchema`
365
+ declares it `secret`), not to plugin KV.
279
366
 
280
367
  ## Alternative install sources
281
368
 
@@ -362,8 +449,10 @@ Node adapter.
362
449
  evaluated document under `document:<route>`.
363
450
  - **Auto-evaluate on save**: emdash's `content:afterSave` hook fires
364
451
  for each save, re-evaluates that one document, and updates its
365
- KV findings entry. The dashboard widget reflects the new score on
366
- the next page load.
452
+ KV findings entry. Findings that depend on other documents, such as
453
+ broken internal links, keep their last Refresh result until the next
454
+ Refresh. The dashboard widget reflects the new score on the next page
455
+ load.
367
456
 
368
457
  ## Architecture in one paragraph
369
458
 
@@ -377,3 +466,32 @@ itself is the same `aexeo-emdash-bridge` Rust crate that powers the
377
466
  Aexeo CLI; it's compiled to WebAssembly via wasm-pack and imported
378
467
  by the host's bundler (Vite + `vite-plugin-wasm`). No separate
379
468
  service, no sidecar, no auth token.
469
+
470
+ ### Exporting a conservative link inventory
471
+
472
+ For host code with access to EmDash's content API, the package exports
473
+ `exportLinkInventory(content, options)`. Select the collections belonging to
474
+ one publication explicitly and supply the observed site identity:
475
+
476
+ ```ts
477
+ import { exportLinkInventory } from "@aeptus/aexeo-emdash";
478
+
479
+ const result = await exportLinkInventory(ctx.content, {
480
+ collections: ["blog_posts", "pages"],
481
+ identity: { origin: "https://example.org", release: "release-id", surface: "public", snapshot: "snapshot-id" },
482
+ });
483
+ // result.inventory matches the link-graph input's inventory shape.
484
+ // Inspect result.issues and result.omitted before using it.
485
+ ```
486
+
487
+ The export resolves public URLs through EmDash and paginates each selected
488
+ collection. Entries without public URLs are reported as omitted, and entries
489
+ with URLs default to `unknown` disposition. A host may supply `classify` when
490
+ it has authoritative indexability and alias information. Repeated or missing
491
+ cursors, failures, and page limits are reported. The inventory is always marked
492
+ incomplete because pagination alone cannot establish a transactional snapshot or
493
+ include routes outside the chosen collections. No HTTP endpoint is installed by
494
+ this API. It cannot by itself establish that a crawl and CMS view describe the
495
+ same publication, release and point in time.
496
+
497
+ An optional `onTelemetry` callback receives only elapsed milliseconds and aggregate counts. It is disabled by default and does not receive URLs or content.
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
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
- intelligence-score widget to an emdash site. Saves auto-evaluate the
6
+ intelligence facts widget to an emdash site. Saves auto-evaluate the
7
7
  changed document; the Refresh button re-evaluates the whole site.
8
8
 
9
9
  ## Install
@@ -43,18 +43,25 @@ troubleshooting list, see [INSTALL.md](./INSTALL.md).
43
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
- - **Dashboard widget**: top-line intelligence score (citation, truth,
47
- answer-pack, external-trust dimensions). Banner appears below 60.
46
+ - **Dashboard widget**: citation readiness, truth consistency and answer
47
+ structure side by side, with the top blockers. There is no composite score
48
+ or quality threshold, and a dimension with nothing to measure reads
49
+ `unknown`.
50
+ - **Entity presence**: Wikipedia is searched on `en.wikipedia.org` only, so
51
+ `not_found` says nothing about other language editions.
48
52
  - **Refresh button**: re-evaluate every document in the configured
49
53
  collections (`posts`, `pages` by default).
50
54
  - **Auto-evaluate on save**: EmDash's `content:afterSave` hook evaluates
51
55
  the saved document and updates its findings. Sandboxed installs need a
52
56
  configured evaluator sidecar; configured installs run evaluation in-process.
57
+ Findings that depend on other documents (broken and weak internal links,
58
+ inbound links, hreflang targets, shared canonicals, duplicate metadata and
59
+ text) stay as the last Refresh stored them until the next Refresh.
53
60
 
54
61
  ## Compatibility
55
62
 
56
- - EmDash `>= 0.41.0 < 0.42.0` (peer dependency; current supported range)
57
- - Runtime integration coverage targets EmDash `0.41.0` via the official
63
+ - EmDash `^1.2.0` (peer dependency; current supported range)
64
+ - Runtime integration coverage targets EmDash `1.2.0` via the official
58
65
  `@emdash-cms/plugin-test` host.
59
66
  - Cloudflare Workers via `@astrojs/cloudflare`. The plugin uses
60
67
  Cloudflare-specific APIs and silently no-ops on the Node adapter.
package/dist/adapter.d.ts CHANGED
@@ -1,10 +1,34 @@
1
1
  import type { EmdashDocument } from "./types.js";
2
+ /** Ordered fields that the host declares constitute a custom block's content. */
3
+ export type CustomBlockField = {
4
+ kind: "paragraph" | "richText";
5
+ field: string;
6
+ optional?: boolean;
7
+ } | {
8
+ kind: "heading";
9
+ field: string;
10
+ level?: 1 | 2 | 3 | 4 | 5 | 6;
11
+ optional?: boolean;
12
+ } | {
13
+ kind: "link";
14
+ field: string;
15
+ hrefField: string;
16
+ optional?: boolean;
17
+ } | {
18
+ kind: "repeat";
19
+ field: string;
20
+ fields: readonly CustomBlockField[];
21
+ optional?: boolean;
22
+ };
23
+ export type CustomBlockMappings = Record<string, readonly CustomBlockField[]>;
2
24
  export interface EmdashContentItem {
3
25
  id: string;
4
26
  type: string;
5
27
  slug: string | null;
6
28
  status: string;
7
29
  locale: string | null;
30
+ /** Host write timestamp. Orders concurrent saves and Refresh sweeps. */
31
+ updatedAt?: string;
8
32
  translationGroup?: string | null;
9
33
  data: Record<string, unknown>;
10
34
  seo?: {
@@ -21,6 +45,8 @@ export interface EmdashContentMeta {
21
45
  title: string;
22
46
  /** Exact URL returned by EmDash, or null when this entry has no public route. */
23
47
  publicUrl: string | null;
48
+ /** The host's `updatedAt` for the row this entry was adapted from. */
49
+ updatedAt?: string;
24
50
  }
25
51
  export interface ContentUrlApi {
26
52
  getPublicUrl(collection: string, id: string): Promise<string | null>;
@@ -44,16 +70,18 @@ export interface AdaptedContent {
44
70
  * accounts for collection URL patterns, locale prefixes, and slash policy;
45
71
  * slug-derived paths are not equivalent to the site's actual public URLs.
46
72
  */
47
- export declare function adaptContentItemWithContext(content: EmdashContentItem, contentApi: ContentUrlApi, defaultLocale?: string): Promise<AdaptedContent>;
73
+ export declare function adaptContentItemWithContext(content: EmdashContentItem, contentApi: ContentUrlApi, defaultLocale?: string, blockMappings?: CustomBlockMappings, contentFields?: readonly string[]): Promise<AdaptedContent>;
48
74
  /**
49
75
  * Synchronous adapter for callers that do not have a host content API.
50
76
  * It deliberately uses a stable identity route; callers should use
51
77
  * adaptContentItemWithContext whenever EmDash can resolve the public URL.
52
78
  */
53
- export declare function contentItemToEmdashDocument(content: EmdashContentItem): EmdashDocument;
79
+ export declare function contentItemToEmdashDocument(content: EmdashContentItem, blockMappings?: CustomBlockMappings): EmdashDocument;
54
80
  export declare function adaptContentItem(content: EmdashContentItem, options?: {
55
81
  route?: string;
56
82
  publicUrl?: string | null;
57
83
  defaultLocale?: string;
84
+ blockMappings?: CustomBlockMappings | undefined;
85
+ contentFields?: readonly string[];
58
86
  }): AdaptedContent;
59
87
  //# sourceMappingURL=adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAC;AAEpE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,GAAG,CAAC,EAAE;QACJ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;CACH;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACrE,eAAe,CACb,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC;QACT,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,KAAK,CAAC;YAClB,EAAE,EAAE,MAAM,CAAC;YACX,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;YACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;YACpB,MAAM,EAAE,MAAM,CAAC;YACf,SAAS,EAAE,MAAM,CAAC;SACnB,CAAC,CAAC;KACJ,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,iBAAiB,EAC1B,UAAU,EAAE,aAAa,EACzB,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,cAAc,CAAC,CA2CzB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,iBAAiB,GACzB,cAAc,CAEhB;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,iBAAiB,EAC1B,OAAO,GAAE;IACP,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACnB,GACL,cAAc,CAgChB"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/dist/admin.d.ts CHANGED
File without changes
@@ -1 +1 @@
1
- {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../src/admin.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,eAAO,MAAM,KAAK;;;;;;;;CAcjB,CAAC"}
1
+ {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../src/admin.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,eAAO,MAAM,KAAK;IAEhB,iBAAiB;IACjB,aAAa;IACb,gBAAgB;IAChB,oBAAoB;IACpB,gBAAgB;IAIhB,WAAW;IAGX,QAAQ;CACT,CAAC"}
package/dist/admin.js CHANGED
@@ -163,7 +163,7 @@ function PillarView({
163
163
  /* @__PURE__ */ jsx(SeverityBadge, { severity: finding.severity }),
164
164
  /* @__PURE__ */ jsx("code", { className: "font-mono text-xs", children: finding.rule_id }),
165
165
  /* @__PURE__ */ jsx("span", { className: "flex-1", children: finding.message }),
166
- finding.layers?.secondaries.length ? /* @__PURE__ */ jsx(SecondariesChips, { layers: finding.layers.secondaries }) : null
166
+ finding.layers?.secondaries?.length ? /* @__PURE__ */ jsx(SecondariesChips, { layers: finding.layers.secondaries }) : null
167
167
  ] }, i)) })
168
168
  ] }),
169
169
  findings.length === 0 ? sitewide.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-kumo-subtle", children: "All clean on this layer." }) : null : /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded border border-kumo-line", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-left text-sm", children: [
@@ -181,7 +181,7 @@ function PillarView({
181
181
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx("code", { className: "rounded bg-kumo-tint px-1.5 py-0.5 font-mono text-xs", children: finding.rule_id }) }),
182
182
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx(SeverityBadge, { severity: finding.severity }) }),
183
183
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: finding.message }),
184
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: finding.layers?.secondaries.length ? /* @__PURE__ */ jsx(SecondariesChips, { layers: finding.layers.secondaries }) : /* @__PURE__ */ jsx("span", { className: "text-xs text-kumo-subtle", children: "\u2014" }) })
184
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: finding.layers?.secondaries?.length ? /* @__PURE__ */ jsx(SecondariesChips, { layers: finding.layers.secondaries }) : /* @__PURE__ */ jsx("span", { className: "text-xs text-kumo-subtle", children: "\u2014" }) })
185
185
  ] }, i);
186
186
  }) })
187
187
  ] }) })
@@ -1,5 +1,11 @@
1
1
  import type { Suppression } from "./suppressions.js";
2
+ import type { ContentPresetOptions } from "./content-evaluator.js";
3
+ import type { CustomBlockMappings } from "./adapter.js";
2
4
  export interface ConfiguredPluginOptions {
5
+ blockMappings?: CustomBlockMappings;
6
+ contentFieldsByCollection?: Record<string, readonly string[]>;
7
+ /** Optional portable preset evaluated in addition to the existing site checks. */
8
+ contentPreset?: ContentPresetOptions;
3
9
  /**
4
10
  * Full override of the swept collections. See `aexeoPlugin` in
5
11
  * src/index.ts for the full precedence story.
@@ -1 +1 @@
1
- {"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA0HrD,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,CA0H3E;AAID,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,mBAAmB,CAAC;AAKxE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,mBAAmB,EAAqB,MAAM,cAAc,CAAC;AAuH3E,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,yBAAyB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;IAC9D,kFAAkF;IAClF,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACrC;;;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,CAmJ3E;eAIc,YAAY"}