@rubytech/create-realagent-code 0.1.116 → 0.1.118
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/package.json +1 -1
- package/payload/platform/neo4j/schema.cypher +104 -0
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/admin/PLUGIN.md +1 -0
- package/payload/platform/plugins/aeo/.claude-plugin/plugin.json +17 -0
- package/payload/platform/plugins/aeo/PLUGIN.md +81 -0
- package/payload/platform/plugins/aeo/mcp/dist/__tests__/audit-heuristics.test.d.ts +2 -0
- package/payload/platform/plugins/aeo/mcp/dist/__tests__/audit-heuristics.test.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/__tests__/audit-heuristics.test.js +121 -0
- package/payload/platform/plugins/aeo/mcp/dist/__tests__/audit-heuristics.test.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/__tests__/schema-mapping.test.d.ts +2 -0
- package/payload/platform/plugins/aeo/mcp/dist/__tests__/schema-mapping.test.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/__tests__/schema-mapping.test.js +129 -0
- package/payload/platform/plugins/aeo/mcp/dist/__tests__/schema-mapping.test.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/aeo/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/index.js +171 -0
- package/payload/platform/plugins/aeo/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/audit-heuristics.d.ts +27 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/audit-heuristics.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/audit-heuristics.js +274 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/audit-heuristics.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/neo4j.js +38 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/schema-mapping.d.ts +48 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/schema-mapping.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/schema-mapping.js +254 -0
- package/payload/platform/plugins/aeo/mcp/dist/lib/schema-mapping.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-audit-page.d.ts +25 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-audit-page.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-audit-page.js +78 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-audit-page.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-emit-jsonld.d.ts +18 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-emit-jsonld.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-emit-jsonld.js +56 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-emit-jsonld.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts +37 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.d.ts.map +1 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js +94 -0
- package/payload/platform/plugins/aeo/mcp/dist/tools/aeo-write-llms-txt.js.map +1 -0
- package/payload/platform/plugins/aeo/mcp/package.json +22 -0
- package/payload/platform/plugins/aeo/mcp/vitest.config.ts +9 -0
- package/payload/platform/plugins/aeo/skills/structured-answer/SKILL.md +53 -0
- package/payload/platform/plugins/docs/PLUGIN.md +1 -0
- package/payload/platform/plugins/docs/references/admin-session.md +1 -1
- package/payload/platform/plugins/docs/references/aeo.md +87 -0
- package/payload/platform/plugins/docs/references/deployment.md +1 -1
- package/payload/platform/plugins/email/PLUGIN.md +6 -0
- package/payload/platform/plugins/email/skills/email-composition/SKILL.md +180 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js +5 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/label-origin-gate.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/label-origin-gate.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/label-origin-gate.test.js +62 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/label-origin-gate.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js +62 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/label-origin-gate.d.ts +18 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/label-origin-gate.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/label-origin-gate.js +29 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/label-origin-gate.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts +3 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +9 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +8 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +4 -0
- package/payload/platform/plugins/memory/references/schema-base.md +5 -1
- package/payload/platform/scripts/smoke-boot-services.sh +30 -2
- package/payload/platform/templates/specialists/agents/signal-detector.md +1 -1
- package/payload/server/public/assets/{admin-DloKrK2i.js → admin-D6IfAzYY.js} +1 -1
- package/payload/server/public/assets/{data-NMvGx6jm.js → data-BGbQyufe.js} +1 -1
- package/payload/server/public/assets/{graph-CN60KyTy.js → graph-D-1lRTeL.js} +1 -1
- package/payload/server/public/assets/{graph-labels-h39S93Xo.js → graph-labels-BRXJHNYE.js} +1 -1
- package/payload/server/public/assets/{page-B0iMmY6t.js → page-B4oirCvn.js} +1 -1
- package/payload/server/public/assets/{page-CFRU-BdJ.js → page-FmJ7PIYx.js} +1 -1
- package/payload/server/public/data.html +3 -3
- package/payload/server/public/graph.html +3 -3
- package/payload/server/public/index.html +4 -4
- package/payload/server/server.js +14 -0
package/package.json
CHANGED
|
@@ -1100,3 +1100,107 @@ FOR (h:CloudflareHostname) REQUIRE (h.accountId, h.hostnameValue) IS UNIQUE;
|
|
|
1100
1100
|
|
|
1101
1101
|
CREATE INDEX cloudflare_hostname_account IF NOT EXISTS
|
|
1102
1102
|
FOR (h:CloudflareHostname) ON (h.accountId);
|
|
1103
|
+
|
|
1104
|
+
// ----------------------------------------------------------
|
|
1105
|
+
// Estate-agent ontology (Task 358) — Listing-centric model
|
|
1106
|
+
// documented at platform/plugins/memory/references/schema-estate-agent.md.
|
|
1107
|
+
//
|
|
1108
|
+
// These declarations exist primarily so the memory-write validator's
|
|
1109
|
+
// parseLabelsFromSchemaCypher() registers each label on a fresh deploy
|
|
1110
|
+
// where no node of the label has yet landed — without them, the first
|
|
1111
|
+
// :Listing write from the listing-curator agent is rejected as "unknown
|
|
1112
|
+
// label" against an empty db.labels() set.
|
|
1113
|
+
//
|
|
1114
|
+
// Idempotency contracts mirror schema-estate-agent.md:
|
|
1115
|
+
// :Listing / :Viewing / :Offer — (accountId, sourceSystem, sourceId)
|
|
1116
|
+
// :Property — (accountId, slug)
|
|
1117
|
+
// :ImageObject — (accountId, listingSlug, url) for the
|
|
1118
|
+
// estate-agent variant, (accountId, name,
|
|
1119
|
+
// contentUrl) for the brand-asset variant.
|
|
1120
|
+
// Because the two writers use different
|
|
1121
|
+
// natural keys, only an accountId index is
|
|
1122
|
+
// declared at the schema level — uniqueness
|
|
1123
|
+
// is enforced at write time by the curator
|
|
1124
|
+
// / brand-asset writer's MERGE clause.
|
|
1125
|
+
// ----------------------------------------------------------
|
|
1126
|
+
|
|
1127
|
+
CREATE CONSTRAINT listing_account_source_unique IF NOT EXISTS
|
|
1128
|
+
FOR (l:Listing) REQUIRE (l.accountId, l.sourceSystem, l.sourceId) IS UNIQUE;
|
|
1129
|
+
|
|
1130
|
+
CREATE INDEX listing_account IF NOT EXISTS
|
|
1131
|
+
FOR (l:Listing) ON (l.accountId);
|
|
1132
|
+
|
|
1133
|
+
CREATE INDEX listing_status IF NOT EXISTS
|
|
1134
|
+
FOR (l:Listing) ON (l.status);
|
|
1135
|
+
|
|
1136
|
+
CREATE INDEX listing_type IF NOT EXISTS
|
|
1137
|
+
FOR (l:Listing) ON (l.listingType);
|
|
1138
|
+
|
|
1139
|
+
CREATE VECTOR INDEX listing_embedding IF NOT EXISTS
|
|
1140
|
+
FOR (l:Listing) ON (l.embedding)
|
|
1141
|
+
OPTIONS {
|
|
1142
|
+
indexConfig: {
|
|
1143
|
+
`vector.dimensions`: 768,
|
|
1144
|
+
`vector.similarity_function`: 'cosine'
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
CREATE CONSTRAINT property_account_slug_unique IF NOT EXISTS
|
|
1149
|
+
FOR (p:Property) REQUIRE (p.accountId, p.slug) IS UNIQUE;
|
|
1150
|
+
|
|
1151
|
+
CREATE INDEX property_account IF NOT EXISTS
|
|
1152
|
+
FOR (p:Property) ON (p.accountId);
|
|
1153
|
+
|
|
1154
|
+
CREATE CONSTRAINT viewing_account_source_unique IF NOT EXISTS
|
|
1155
|
+
FOR (v:Viewing) REQUIRE (v.accountId, v.sourceSystem, v.sourceId) IS UNIQUE;
|
|
1156
|
+
|
|
1157
|
+
CREATE INDEX viewing_account IF NOT EXISTS
|
|
1158
|
+
FOR (v:Viewing) ON (v.accountId);
|
|
1159
|
+
|
|
1160
|
+
CREATE CONSTRAINT offer_account_source_unique IF NOT EXISTS
|
|
1161
|
+
FOR (o:Offer) REQUIRE (o.accountId, o.sourceSystem, o.sourceId) IS UNIQUE;
|
|
1162
|
+
|
|
1163
|
+
CREATE INDEX offer_account IF NOT EXISTS
|
|
1164
|
+
FOR (o:Offer) ON (o.accountId);
|
|
1165
|
+
|
|
1166
|
+
CREATE INDEX image_object_account IF NOT EXISTS
|
|
1167
|
+
FOR (i:ImageObject) ON (i.accountId);
|
|
1168
|
+
|
|
1169
|
+
// :PostalAddress (schema:PostalAddress) — shared address nodes documented in
|
|
1170
|
+
// schema-base.md. Address rows are deliberately accountId-free (one address
|
|
1171
|
+
// can be shared across customers / orders / listings); writers MERGE on the
|
|
1172
|
+
// formatted address triple. Declared here so a fresh deploy registers the
|
|
1173
|
+
// label before the first :PostalAddress write lands.
|
|
1174
|
+
CREATE INDEX postal_address_postcode IF NOT EXISTS
|
|
1175
|
+
FOR (a:PostalAddress) ON (a.postalCode);
|
|
1176
|
+
|
|
1177
|
+
// ----------------------------------------------------------
|
|
1178
|
+
// AEOAudit — Answer Engine Optimisation audit history (Task 354).
|
|
1179
|
+
// Written by the aeo plugin's aeo-audit-page tool when invoked
|
|
1180
|
+
// with persist=true. Append-only audit trail; each invocation
|
|
1181
|
+
// produces a new node. Linked via
|
|
1182
|
+
// (KnowledgeDocument)-[:HAS_AEO_AUDIT]->(AEOAudit) when the audit
|
|
1183
|
+
// targets a stored document, otherwise standalone keyed on
|
|
1184
|
+
// (accountId, target, runAt).
|
|
1185
|
+
//
|
|
1186
|
+
// Properties: accountId, auditId (uuid), target (url or
|
|
1187
|
+
// '(inline html)'), score (0-100), heuristicsJson (serialized
|
|
1188
|
+
// per-heuristic result), runAt, createdByAgent, createdBySession,
|
|
1189
|
+
// createdAt.
|
|
1190
|
+
//
|
|
1191
|
+
// Monitor-related labels (:AEOPrompt, :AEOCheck, :AEOCitation) are
|
|
1192
|
+
// intentionally NOT declared in v1 — the multi-engine monitor is
|
|
1193
|
+
// filed as a follow-up task and the schema will land with it.
|
|
1194
|
+
// ----------------------------------------------------------
|
|
1195
|
+
|
|
1196
|
+
CREATE CONSTRAINT aeo_audit_id_unique IF NOT EXISTS
|
|
1197
|
+
FOR (a:AEOAudit) REQUIRE a.auditId IS UNIQUE;
|
|
1198
|
+
|
|
1199
|
+
CREATE INDEX aeo_audit_account IF NOT EXISTS
|
|
1200
|
+
FOR (a:AEOAudit) ON (a.accountId);
|
|
1201
|
+
|
|
1202
|
+
CREATE INDEX aeo_audit_target IF NOT EXISTS
|
|
1203
|
+
FOR (a:AEOAudit) ON (a.target);
|
|
1204
|
+
|
|
1205
|
+
CREATE INDEX aeo_audit_run_at IF NOT EXISTS
|
|
1206
|
+
FOR (a:AEOAudit) ON (a.runAt);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: admin
|
|
3
|
+
surface: platform
|
|
3
4
|
description: "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), brand-settings, account-manage, account-update, admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, file-attach, wifi, action-approval tools (action-pending, action-approve, action-reject, action-edit), and session-retrospective-mark-complete (the deterministic sentinel the session-end Stop hook reads via JSONL grep to release the retrospective gate; admin calls it once per session after running the three-pass retrospective) for managing the Maxy platform."
|
|
4
5
|
tools:
|
|
5
6
|
- name: system-status
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aeo",
|
|
3
|
+
"description": "Answer Engine Optimisation. Emits schema.org JSON-LD for typed graph entities, generates llms.txt + llms-full.txt for the site, and audits pages against eight AEO heuristics with one rewrite suggestion per fail. Writes :AEOAudit history nodes when audits are persisted.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Rubytech LLC"
|
|
7
|
+
},
|
|
8
|
+
"mcpServers": {
|
|
9
|
+
"aeo": {
|
|
10
|
+
"type": "stdio",
|
|
11
|
+
"command": "node",
|
|
12
|
+
"args": [
|
|
13
|
+
"${CLAUDE_PLUGIN_ROOT}/mcp/dist/index.js"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aeo
|
|
3
|
+
description: "Answer Engine Optimisation. Emits schema.org JSON-LD for typed graph entities, generates llms.txt + llms-full.txt for the site, and audits pages against eight AEO heuristics with one rewrite suggestion per fail. Writes :AEOAudit history nodes when audits are persisted."
|
|
4
|
+
tools:
|
|
5
|
+
- name: aeo-emit-jsonld
|
|
6
|
+
publicAllowlist: false
|
|
7
|
+
adminAllowlist: true
|
|
8
|
+
- name: aeo-write-llms-txt
|
|
9
|
+
publicAllowlist: false
|
|
10
|
+
adminAllowlist: true
|
|
11
|
+
- name: aeo-audit-page
|
|
12
|
+
publicAllowlist: false
|
|
13
|
+
adminAllowlist: true
|
|
14
|
+
skills:
|
|
15
|
+
- skills/structured-answer/SKILL.md
|
|
16
|
+
always: false
|
|
17
|
+
embed: false
|
|
18
|
+
metadata: {"platform":{}}
|
|
19
|
+
mcp:
|
|
20
|
+
command: node
|
|
21
|
+
args:
|
|
22
|
+
- ${PLATFORM_ROOT}/lib/mcp-spawn-tee/dist/index.js
|
|
23
|
+
- ${PLATFORM_ROOT}/plugins/aeo/mcp/dist/index.js
|
|
24
|
+
env:
|
|
25
|
+
MCP_SPAWN_TEE_NAME: aeo
|
|
26
|
+
LOG_DIR: ${LOG_DIR}
|
|
27
|
+
PLATFORM_ROOT: ${PLATFORM_ROOT}
|
|
28
|
+
ACCOUNT_ID: ${ACCOUNT_ID}
|
|
29
|
+
SESSION_ID: ${SESSION_ID}
|
|
30
|
+
AGENT_SLUG: ${AGENT_SLUG}
|
|
31
|
+
mcp-manifest: auto
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
# AEO (Answer Engine Optimisation)
|
|
35
|
+
|
|
36
|
+
This plugin makes the platform's content legible to answer engines — Claude, ChatGPT, Perplexity, Google AI Overviews, Bing Copilot. Classical SEO ranks links; AEO shapes the page so the answer-engine extractor lifts the right answer.
|
|
37
|
+
|
|
38
|
+
## The three surfaces
|
|
39
|
+
|
|
40
|
+
1. **JSON-LD** (`aeo-emit-jsonld`). Every typed graph entity that backs a page can be projected to a `<script type="application/ld+json">` block. The mapper covers `Organization`, `LocalBusiness`, `Person`, `Service`, `Product`, `Article`, `FAQPage`, `RealEstateListing`, `Event`. Other labels fall through to `schema:Thing` and are flagged as a doc gap.
|
|
41
|
+
|
|
42
|
+
2. **llms.txt** (`aeo-write-llms-txt`). The site emits `/llms.txt` (index of pages, one link per line) and `/llms-full.txt` (concatenated full text). Format follows the current draft at `https://llmstxt.org/`. Source is `KnowledgeDocument` nodes with a `url` property. Pages without a `url` are skipped and counted in the response.
|
|
43
|
+
|
|
44
|
+
3. **Audit** (`aeo-audit-page`). Run the eight-heuristic scorecard against a URL or raw HTML. Each heuristic returns pass / warn / fail with one rewrite suggestion per fail. Score is `100 * (passes + 0.5 * warns) / 8`. Pass `persist: true` with a `targetKnowledgeDocumentId` to record an `:AEOAudit` node linked back to the document.
|
|
45
|
+
|
|
46
|
+
## The eight heuristics
|
|
47
|
+
|
|
48
|
+
| Heuristic | Pass | Warn | Fail |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| `h1-present` | one `<h1>` | more than one | none |
|
|
51
|
+
| `jsonld-present` | one or more JSON-LD blocks, all parse | some parse | none, or all unparseable |
|
|
52
|
+
| `structured-answer` | first `<p>` after `<h1>` is ≤280 chars | first `<p>` is over 280 chars | no `<p>` after `<h1>` |
|
|
53
|
+
| `faq-section` | `FAQPage` JSON-LD on page | FAQ text but no JSON-LD | nothing FAQ-like |
|
|
54
|
+
| `meta-description` | 80–160 chars | present but outside range | absent |
|
|
55
|
+
| `canonical-url` | `<link rel="canonical">` present | — | absent |
|
|
56
|
+
| `og-tags` | `og:title` and `og:description` both set | — | one or both missing |
|
|
57
|
+
| `heading-hierarchy` | no level skips | — | h1→h3 or similar jump |
|
|
58
|
+
|
|
59
|
+
`structured-answer` is the highest-impact: answer engines lift the structured-answer span verbatim. A page can fail every other heuristic and still get cited if the first paragraph cleanly answers the question.
|
|
60
|
+
|
|
61
|
+
## Persistence model
|
|
62
|
+
|
|
63
|
+
The plugin only writes `:AEOAudit` nodes (when `persist: true`). It does not own the entities it reads — `KnowledgeDocument`, `Organization`, `Person`, etc. belong to the memory plugin. Edges written:
|
|
64
|
+
|
|
65
|
+
- `(:KnowledgeDocument)-[:HAS_AEO_AUDIT]->(:AEOAudit)`
|
|
66
|
+
|
|
67
|
+
The audit nodes carry `accountId`, `target` (URL or `(inline html)`), `score`, `heuristicsJson` (full result), `runAt`, `createdByAgent`, `createdBySession`. They form an append-only audit history; aggregate trend queries (score-over-time) walk this set.
|
|
68
|
+
|
|
69
|
+
## What this plugin does not do
|
|
70
|
+
|
|
71
|
+
- No LLM API calls. There is no monitor in v1. The "is my brand cited when a customer asks Claude / ChatGPT / Perplexity / Gemini" question depends on multi-engine answer harvesting that runs differently under maxy-code's no-API-key architecture, and is filed as a follow-up task.
|
|
72
|
+
- No automatic emission. `aeo-emit-jsonld` returns a script block; wiring it into the platform's page render path is per-renderer work, also filed as a follow-up.
|
|
73
|
+
- No publish-hook regeneration of `llms.txt`. The tool runs on demand. Hooking it into the publish event is a follow-up.
|
|
74
|
+
- No prose authoring. AEO audits structure; it does not rewrite content. Writer-craft owns voice.
|
|
75
|
+
|
|
76
|
+
## See also
|
|
77
|
+
|
|
78
|
+
- User guide: `platform/plugins/docs/references/aeo.md`
|
|
79
|
+
- Structured-answer template: `platform/plugins/aeo/skills/structured-answer/SKILL.md`
|
|
80
|
+
- Schema declaration: `platform/neo4j/schema.cypher` (search `AEOAudit`)
|
|
81
|
+
- Schema reference: `platform/plugins/memory/schema-base.md` (search `AEOAudit`)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-heuristics.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/audit-heuristics.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { auditHtml } from "../lib/audit-heuristics.js";
|
|
3
|
+
function pick(result, name) {
|
|
4
|
+
const r = result.heuristics.find((h) => h.name === name);
|
|
5
|
+
if (!r)
|
|
6
|
+
throw new Error(`heuristic ${name} not found`);
|
|
7
|
+
return r;
|
|
8
|
+
}
|
|
9
|
+
const GOOD_PAGE = `<!doctype html>
|
|
10
|
+
<html><head>
|
|
11
|
+
<title>How much does a boiler service cost? — Acme</title>
|
|
12
|
+
<meta name="description" content="A standard boiler service in Brighton costs £85 to £120, takes about one hour, and includes a written safety report.">
|
|
13
|
+
<link rel="canonical" href="https://acme.test/boiler-service-cost">
|
|
14
|
+
<meta property="og:title" content="How much does a boiler service cost?">
|
|
15
|
+
<meta property="og:description" content="A standard boiler service in Brighton costs £85 to £120, takes about one hour, and includes a written safety report.">
|
|
16
|
+
<script type="application/ld+json">{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{"@type":"Question","name":"Cost?","acceptedAnswer":{"@type":"Answer","text":"£85–£120."}}]}</script>
|
|
17
|
+
</head><body>
|
|
18
|
+
<h1>How much does a boiler service cost?</h1>
|
|
19
|
+
<p>A standard boiler service in Brighton costs £85 to £120 and takes about one hour.</p>
|
|
20
|
+
<h2>What's included</h2>
|
|
21
|
+
<p>Safety checks, flue gas analysis, pressure adjustment, written report.</p>
|
|
22
|
+
<h2>Frequently asked questions</h2>
|
|
23
|
+
<p>Common questions are answered above.</p>
|
|
24
|
+
</body></html>`;
|
|
25
|
+
const BAD_PAGE = `<!doctype html>
|
|
26
|
+
<html><head><title>Acme</title></head>
|
|
27
|
+
<body>
|
|
28
|
+
<div>Some words.</div>
|
|
29
|
+
</body></html>`;
|
|
30
|
+
describe("auditHtml — golden good page", () => {
|
|
31
|
+
const result = auditHtml(GOOD_PAGE);
|
|
32
|
+
it("scores 100", () => {
|
|
33
|
+
expect(result.score).toBe(100);
|
|
34
|
+
});
|
|
35
|
+
it("passes every heuristic", () => {
|
|
36
|
+
for (const h of result.heuristics) {
|
|
37
|
+
expect(h.status, `${h.name}: ${h.detail}`).toBe("pass");
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe("auditHtml — bad page exercises every fail path", () => {
|
|
42
|
+
const result = auditHtml(BAD_PAGE);
|
|
43
|
+
it("scores 0 or near-zero", () => {
|
|
44
|
+
expect(result.score).toBeLessThanOrEqual(10);
|
|
45
|
+
});
|
|
46
|
+
it("fails h1-present", () => {
|
|
47
|
+
expect(pick(result, "h1-present").status).toBe("fail");
|
|
48
|
+
});
|
|
49
|
+
it("fails jsonld-present", () => {
|
|
50
|
+
expect(pick(result, "jsonld-present").status).toBe("fail");
|
|
51
|
+
});
|
|
52
|
+
it("fails meta-description", () => {
|
|
53
|
+
expect(pick(result, "meta-description").status).toBe("fail");
|
|
54
|
+
});
|
|
55
|
+
it("fails canonical-url", () => {
|
|
56
|
+
expect(pick(result, "canonical-url").status).toBe("fail");
|
|
57
|
+
});
|
|
58
|
+
it("fails og-tags with both missing", () => {
|
|
59
|
+
const r = pick(result, "og-tags");
|
|
60
|
+
expect(r.status).toBe("fail");
|
|
61
|
+
expect(r.detail).toContain("og:title");
|
|
62
|
+
expect(r.detail).toContain("og:description");
|
|
63
|
+
});
|
|
64
|
+
it("emits a rewrite suggestion for every failure", () => {
|
|
65
|
+
for (const h of result.heuristics) {
|
|
66
|
+
if (h.status === "fail") {
|
|
67
|
+
expect(h.suggestion, `${h.name} missing suggestion`).toBeTruthy();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe("auditHtml — warn paths", () => {
|
|
73
|
+
it("warns when first <p> after <h1> exceeds 280 chars", () => {
|
|
74
|
+
const long = "X".repeat(300);
|
|
75
|
+
const html = `<html><head>
|
|
76
|
+
<title>t</title>
|
|
77
|
+
<meta name="description" content="${"d".repeat(120)}">
|
|
78
|
+
<link rel="canonical" href="https://a.test">
|
|
79
|
+
<meta property="og:title" content="t">
|
|
80
|
+
<meta property="og:description" content="t">
|
|
81
|
+
<script type="application/ld+json">{"@type":"FAQPage"}</script>
|
|
82
|
+
</head><body>
|
|
83
|
+
<h1>Topic</h1><p>${long}</p>
|
|
84
|
+
<h2>More</h2><p>x</p>
|
|
85
|
+
</body></html>`;
|
|
86
|
+
const result = auditHtml(html);
|
|
87
|
+
expect(pick(result, "structured-answer").status).toBe("warn");
|
|
88
|
+
});
|
|
89
|
+
it("warns when meta-description length is outside 80–160", () => {
|
|
90
|
+
const html = `<html><head>
|
|
91
|
+
<title>t</title>
|
|
92
|
+
<meta name="description" content="too short">
|
|
93
|
+
<link rel="canonical" href="https://a.test">
|
|
94
|
+
<meta property="og:title" content="t">
|
|
95
|
+
<meta property="og:description" content="t">
|
|
96
|
+
<script type="application/ld+json">{"@type":"FAQPage"}</script>
|
|
97
|
+
</head><body>
|
|
98
|
+
<h1>Topic</h1><p>short answer</p>
|
|
99
|
+
</body></html>`;
|
|
100
|
+
expect(pick(auditHtml(html), "meta-description").status).toBe("warn");
|
|
101
|
+
});
|
|
102
|
+
it("warns when there are multiple <h1> elements", () => {
|
|
103
|
+
const html = `<html><body><h1>A</h1><h1>B</h1><p>x</p></body></html>`;
|
|
104
|
+
expect(pick(auditHtml(html), "h1-present").status).toBe("warn");
|
|
105
|
+
});
|
|
106
|
+
it("fails heading-hierarchy on h1→h3 skip", () => {
|
|
107
|
+
const html = `<html><body><h1>A</h1><p>x</p><h3>Skipped</h3></body></html>`;
|
|
108
|
+
const r = pick(auditHtml(html), "heading-hierarchy");
|
|
109
|
+
expect(r.status).toBe("fail");
|
|
110
|
+
expect(r.detail).toContain("h1");
|
|
111
|
+
expect(r.detail).toContain("h3");
|
|
112
|
+
});
|
|
113
|
+
it("warns on unparseable JSON-LD when other blocks parse", () => {
|
|
114
|
+
const html = `<html><head>
|
|
115
|
+
<script type="application/ld+json">{"@type":"FAQPage"}</script>
|
|
116
|
+
<script type="application/ld+json">not json</script>
|
|
117
|
+
</head><body><h1>T</h1><p>a</p></body></html>`;
|
|
118
|
+
expect(pick(auditHtml(html), "jsonld-present").status).toBe("warn");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
//# sourceMappingURL=audit-heuristics.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-heuristics.test.js","sourceRoot":"","sources":["../../src/__tests__/audit-heuristics.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-mapping.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/schema-mapping.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { buildJsonLd, renderJsonLdScript, resolveSchemaType, MissingPropertyError, } from "../lib/schema-mapping.js";
|
|
3
|
+
describe("resolveSchemaType", () => {
|
|
4
|
+
it("maps known graph labels to schema.org types", () => {
|
|
5
|
+
expect(resolveSchemaType("LocalBusiness")).toBe("LocalBusiness");
|
|
6
|
+
expect(resolveSchemaType("Organization")).toBe("Organization");
|
|
7
|
+
expect(resolveSchemaType("Person")).toBe("Person");
|
|
8
|
+
expect(resolveSchemaType("Service")).toBe("Service");
|
|
9
|
+
expect(resolveSchemaType("Product")).toBe("Product");
|
|
10
|
+
expect(resolveSchemaType("CreativeWork")).toBe("Article");
|
|
11
|
+
expect(resolveSchemaType("Article")).toBe("Article");
|
|
12
|
+
expect(resolveSchemaType("FAQPage")).toBe("FAQPage");
|
|
13
|
+
expect(resolveSchemaType("Listing")).toBe("RealEstateListing");
|
|
14
|
+
expect(resolveSchemaType("RealEstateListing")).toBe("RealEstateListing");
|
|
15
|
+
expect(resolveSchemaType("Event")).toBe("Event");
|
|
16
|
+
});
|
|
17
|
+
it("falls back to Thing for unknown labels", () => {
|
|
18
|
+
expect(resolveSchemaType("UnknownLabel")).toBe("Thing");
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe("buildJsonLd", () => {
|
|
22
|
+
it("renders an Organization with required name", () => {
|
|
23
|
+
const obj = buildJsonLd({
|
|
24
|
+
label: "Organization",
|
|
25
|
+
properties: { name: "Acme", website: "https://acme.test" },
|
|
26
|
+
});
|
|
27
|
+
expect(obj["@context"]).toBe("https://schema.org");
|
|
28
|
+
expect(obj["@type"]).toBe("Organization");
|
|
29
|
+
expect(obj.name).toBe("Acme");
|
|
30
|
+
expect(obj.url).toBe("https://acme.test");
|
|
31
|
+
});
|
|
32
|
+
it("renders a LocalBusiness with nested PostalAddress", () => {
|
|
33
|
+
const obj = buildJsonLd({
|
|
34
|
+
label: "LocalBusiness",
|
|
35
|
+
elementId: "4:abc:1",
|
|
36
|
+
properties: {
|
|
37
|
+
name: "Acme Plumbing",
|
|
38
|
+
streetAddress: "1 High St",
|
|
39
|
+
addressLocality: "Brighton",
|
|
40
|
+
postalCode: "BN1 1AA",
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
expect(obj["@id"]).toBe("4:abc:1");
|
|
44
|
+
expect(obj.address).toEqual({
|
|
45
|
+
"@type": "PostalAddress",
|
|
46
|
+
streetAddress: "1 High St",
|
|
47
|
+
addressLocality: "Brighton",
|
|
48
|
+
postalCode: "BN1 1AA",
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
it("renders a Person and synthesises name from givenName + familyName", () => {
|
|
52
|
+
const obj = buildJsonLd({
|
|
53
|
+
label: "Person",
|
|
54
|
+
properties: {
|
|
55
|
+
givenName: "Joel",
|
|
56
|
+
familyName: "Smalley",
|
|
57
|
+
email: "j@example.com",
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
expect(obj["@type"]).toBe("Person");
|
|
61
|
+
expect(obj.name).toBe("Joel Smalley");
|
|
62
|
+
expect(obj.email).toBe("j@example.com");
|
|
63
|
+
});
|
|
64
|
+
it("renders a Service with an Offer when price is set", () => {
|
|
65
|
+
const obj = buildJsonLd({
|
|
66
|
+
label: "Service",
|
|
67
|
+
properties: { name: "Boiler service", price: 120, priceCurrency: "GBP" },
|
|
68
|
+
});
|
|
69
|
+
expect(obj.offers).toEqual({
|
|
70
|
+
"@type": "Offer",
|
|
71
|
+
price: 120,
|
|
72
|
+
priceCurrency: "GBP",
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
it("renders an Article remapping title→headline and abstract→description", () => {
|
|
76
|
+
const obj = buildJsonLd({
|
|
77
|
+
label: "Article",
|
|
78
|
+
properties: { title: "How boilers work", abstract: "Short summary" },
|
|
79
|
+
});
|
|
80
|
+
expect(obj.headline).toBe("How boilers work");
|
|
81
|
+
expect(obj.description).toBe("Short summary");
|
|
82
|
+
});
|
|
83
|
+
it("renders an FAQPage with mainEntity Question/Answer pairs", () => {
|
|
84
|
+
const obj = buildJsonLd({
|
|
85
|
+
label: "FAQPage",
|
|
86
|
+
properties: {
|
|
87
|
+
name: "Boiler FAQ",
|
|
88
|
+
questions: [
|
|
89
|
+
{ name: "How long does it take?", acceptedAnswer: "About an hour." },
|
|
90
|
+
{ name: "Do you charge call-out?", acceptedAnswer: "No." },
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
expect(Array.isArray(obj.mainEntity)).toBe(true);
|
|
95
|
+
const main = obj.mainEntity;
|
|
96
|
+
expect(main).toHaveLength(2);
|
|
97
|
+
});
|
|
98
|
+
it("renders an Event with required name + startDate", () => {
|
|
99
|
+
const obj = buildJsonLd({
|
|
100
|
+
label: "Event",
|
|
101
|
+
properties: { name: "Open day", startDate: "2026-06-01T10:00:00Z" },
|
|
102
|
+
});
|
|
103
|
+
expect(obj.startDate).toBe("2026-06-01T10:00:00Z");
|
|
104
|
+
});
|
|
105
|
+
it("throws MissingPropertyError when required property is absent", () => {
|
|
106
|
+
expect(() => buildJsonLd({ label: "Event", properties: { name: "Open day" } })).toThrow(MissingPropertyError);
|
|
107
|
+
});
|
|
108
|
+
it("renders unknown labels as schema:Thing without throwing", () => {
|
|
109
|
+
const obj = buildJsonLd({
|
|
110
|
+
label: "Mystery",
|
|
111
|
+
properties: { name: "Anonymous", description: "Unknown" },
|
|
112
|
+
});
|
|
113
|
+
expect(obj["@type"]).toBe("Thing");
|
|
114
|
+
expect(obj.name).toBe("Anonymous");
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
describe("renderJsonLdScript", () => {
|
|
118
|
+
it("wraps the JSON in a script block", () => {
|
|
119
|
+
const obj = buildJsonLd({
|
|
120
|
+
label: "Organization",
|
|
121
|
+
properties: { name: "Acme" },
|
|
122
|
+
});
|
|
123
|
+
const block = renderJsonLdScript(obj);
|
|
124
|
+
expect(block).toMatch(/^<script type="application\/ld\+json">/);
|
|
125
|
+
expect(block).toMatch(/<\/script>$/);
|
|
126
|
+
expect(block).toContain('"Acme"');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
//# sourceMappingURL=schema-mapping.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-mapping.test.js","sourceRoot":"","sources":["../../src/__tests__/schema-mapping.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|