@operstack/audit 0.25.0 → 0.26.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/package.json +1 -1
- package/src/prompts.mjs +78 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@operstack/audit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "SEO, AEO and GEO audit generator: collect public signals from a site into audit.json, let an analyst or an agent fill the narrative, render the OperStack report as HTML and PDF.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/prompts.mjs
CHANGED
|
@@ -128,6 +128,80 @@ const HEAD = {
|
|
|
128
128
|
* @param {object} check строка проверки из аудита
|
|
129
129
|
* @param {{lang?: 'ru'|'en', urls?: string[]}} opts
|
|
130
130
|
*/
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Где именно делать правку, смотря на чём собран сайт.
|
|
134
|
+
*
|
|
135
|
+
* Простым языком. Задание «поставьте разметку организации» верно для всех, но человек на WordPress
|
|
136
|
+
* спрашивает «где?», и человек на Astro спрашивает то же про другое место. Раньше он искал сам.
|
|
137
|
+
* Здесь названо конкретное место для четырёх самых частых случаев.
|
|
138
|
+
*
|
|
139
|
+
* Платформа не угадывается: берётся из проверки `cms`, то есть из того, что сайт сам о себе сказал
|
|
140
|
+
* в мета-теге generator. Молчит, значит показываем все варианты, а не выбираем наугад.
|
|
141
|
+
*
|
|
142
|
+
* Добавлено 17.09.2026 после разбора конкурентов: у обоих код правки даётся под конкретный стек, и
|
|
143
|
+
* это единственное место, где их подсказка была полезнее нашей.
|
|
144
|
+
*/
|
|
145
|
+
const WHERE = {
|
|
146
|
+
'org-schema': {
|
|
147
|
+
wordpress: { ru: 'тема: header.php или functions.php через wp_head, либо плагин разметки', en: 'theme: header.php, or functions.php via wp_head, or a schema plugin' },
|
|
148
|
+
astro: { ru: 'src/layouts, ваш общий макет, внутрь <head>', en: 'src/layouts, your shared layout, inside <head>' },
|
|
149
|
+
next: { ru: 'app/layout.tsx, тег <script type="application/ld+json">', en: 'app/layout.tsx, a <script type="application/ld+json"> tag' },
|
|
150
|
+
shopify: { ru: 'theme.liquid, перед </head>', en: 'theme.liquid, before </head>' },
|
|
151
|
+
},
|
|
152
|
+
'og-title': {
|
|
153
|
+
wordpress: { ru: 'плагин SEO (Yoast, Rank Math) заполняет og:title из заголовка записи', en: 'an SEO plugin (Yoast, Rank Math) fills og:title from the post title' },
|
|
154
|
+
astro: { ru: 'общий макет, где собирается <head>', en: 'the shared layout where <head> is assembled' },
|
|
155
|
+
next: { ru: 'export const metadata, поле openGraph.title', en: 'export const metadata, the openGraph.title field' },
|
|
156
|
+
shopify: { ru: 'theme.liquid, секция мета-тегов', en: 'theme.liquid, the meta tag section' },
|
|
157
|
+
},
|
|
158
|
+
'og-image': {
|
|
159
|
+
wordpress: { ru: 'изображение записи подставляется в og:image плагином SEO', en: 'the featured image becomes og:image through the SEO plugin' },
|
|
160
|
+
astro: { ru: 'общий макет, поле ogImage у страницы', en: 'the shared layout, the page ogImage field' },
|
|
161
|
+
next: { ru: 'export const metadata, поле openGraph.images', en: 'export const metadata, the openGraph.images field' },
|
|
162
|
+
shopify: { ru: 'theme.liquid, мета-теги', en: 'theme.liquid, the meta tags' },
|
|
163
|
+
},
|
|
164
|
+
canonical: {
|
|
165
|
+
wordpress: { ru: 'плагин SEO ставит canonical сам; проверьте, не выключен ли он для этого типа записей', en: 'the SEO plugin sets canonical; check it is not switched off for this post type' },
|
|
166
|
+
astro: { ru: 'общий макет, <link rel="canonical">', en: 'the shared layout, <link rel="canonical">' },
|
|
167
|
+
next: { ru: 'export const metadata, поле alternates.canonical', en: 'export const metadata, the alternates.canonical field' },
|
|
168
|
+
shopify: { ru: 'theme.liquid, {{ canonical_url }}', en: 'theme.liquid, {{ canonical_url }}' },
|
|
169
|
+
},
|
|
170
|
+
favicon: {
|
|
171
|
+
wordpress: { ru: 'Внешний вид, Свойства сайта, Значок сайта', en: 'Appearance, Site Identity, Site Icon' },
|
|
172
|
+
astro: { ru: 'public/favicon.svg и <link rel="icon"> в макете', en: 'public/favicon.svg and <link rel="icon"> in the layout' },
|
|
173
|
+
next: { ru: 'app/icon.png или app/favicon.ico', en: 'app/icon.png or app/favicon.ico' },
|
|
174
|
+
shopify: { ru: 'Настройки темы, Favicon', en: 'Theme settings, Favicon' },
|
|
175
|
+
},
|
|
176
|
+
'redirect-chain': {
|
|
177
|
+
wordpress: { ru: 'плагин переадресаций: замените цепочку одним правилом на конечный адрес', en: 'the redirect plugin: replace the chain with one rule to the final address' },
|
|
178
|
+
astro: { ru: 'vercel.json или netlify.toml, раздел redirects', en: 'vercel.json or netlify.toml, the redirects section' },
|
|
179
|
+
next: { ru: 'next.config.js, функция redirects()', en: 'next.config.js, the redirects() function' },
|
|
180
|
+
shopify: { ru: 'Интернет-магазин, Навигация, Переадресации адресов', en: 'Online Store, Navigation, URL Redirects' },
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const PLATFORM_LABEL = { wordpress: 'WordPress', astro: 'Astro', next: 'Next.js', shopify: 'Shopify' };
|
|
185
|
+
|
|
186
|
+
/** На чём собран сайт, по словам самого сайта. Не угадываем: молчит, значит показываем все. */
|
|
187
|
+
export function detectPlatform(audit) {
|
|
188
|
+
const cms = (audit?.checks || []).find((c) => c.id === 'cms');
|
|
189
|
+
const said = String(cms?.value || '').toLowerCase();
|
|
190
|
+
if (/wordpress|woocommerce/.test(said)) return 'wordpress';
|
|
191
|
+
if (/astro/.test(said)) return 'astro';
|
|
192
|
+
if (/next/.test(said)) return 'next';
|
|
193
|
+
if (/shopify/.test(said)) return 'shopify';
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function whereLine(id, platform, lang) {
|
|
198
|
+
const map = WHERE[id];
|
|
199
|
+
if (!map) return '';
|
|
200
|
+
const head = lang === 'ru' ? 'Где' : 'Where';
|
|
201
|
+
if (platform && map[platform]) return `**${head}:** ${PLATFORM_LABEL[platform]}, ${map[platform][lang]}`;
|
|
202
|
+
return `**${head}:** ${Object.entries(map).map(([k, v]) => `${PLATFORM_LABEL[k]}, ${v[lang]}`).join('; ')}`;
|
|
203
|
+
}
|
|
204
|
+
|
|
131
205
|
export function agentPrompt(check, opts = {}) {
|
|
132
206
|
const lang = opts.lang === 'ru' ? 'ru' : 'en';
|
|
133
207
|
const h = HEAD[lang];
|
|
@@ -138,6 +212,8 @@ export function agentPrompt(check, opts = {}) {
|
|
|
138
212
|
const lines = [`### ${check.label}`, '', `**${h.now}:** ${check.value}`, '', `**${h.task}:** ${own ? own.task : fallback}`];
|
|
139
213
|
// Проверка обязательна у каждого задания: без неё агент не знает, когда остановиться.
|
|
140
214
|
lines.push('', `**${h.verify}:** ${own ? own.verify : h.genericVerify}`);
|
|
215
|
+
const where = whereLine(check.id, opts.platform || null, lang);
|
|
216
|
+
if (where) lines.push('', where);
|
|
141
217
|
if (opts.urls && opts.urls.length) { lines.push('', `**${h.pages}:** ${opts.urls.slice(0, 8).join(', ')}`); }
|
|
142
218
|
lines.push('', h.rule);
|
|
143
219
|
return lines.join('\n');
|
|
@@ -153,7 +229,8 @@ export function agentPrompts(audit, opts = {}) {
|
|
|
153
229
|
const checks = source.filter((c) => c.status === 'bad' || c.status === 'warn');
|
|
154
230
|
const ordered = [...checks.filter((c) => c.status === 'bad'), ...checks.filter((c) => c.status === 'warn')];
|
|
155
231
|
const chosen = limit > 0 ? ordered.slice(0, limit) : ordered;
|
|
156
|
-
|
|
232
|
+
const platform = opts.platform || detectPlatform(audit);
|
|
233
|
+
return chosen.map((c) => ({ id: c.id, hasOwnText: Boolean(P[c.id]), text: agentPrompt(c, { lang, platform }) }));
|
|
157
234
|
}
|
|
158
235
|
|
|
159
236
|
/** Файл, который отдаётся клиенту вместе с отчётом. */
|