@svelte-vitals/core 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/dist/index.d.ts CHANGED
@@ -23,6 +23,15 @@ interface Project {
23
23
  htmlLang: Detection;
24
24
  }
25
25
  declare const defaultProject: Project;
26
+ /** A concrete, agent-actionable remediation for a finding (design §10, issue #18). */
27
+ interface Fix {
28
+ /** One-line imperative instruction, e.g. 'Add a <meta name="description"> inside <svelte:head>.' */
29
+ description: string;
30
+ /** Concrete code to insert or a file's contents to create. */
31
+ snippet?: string;
32
+ /** Markdown fenced-code language for `snippet` (default 'svelte'). */
33
+ lang?: string;
34
+ }
26
35
  /** A single rule finding for one route (or the whole project). */
27
36
  interface Result {
28
37
  /** Rule id, e.g. 'SEO001'. */
@@ -36,6 +45,8 @@ interface Result {
36
45
  message: string;
37
46
  recommendation?: string;
38
47
  docsUrl?: string;
48
+ /** Agent-actionable remediation (issue #18). */
49
+ fix?: Fix;
39
50
  }
40
51
  type Scope = 'route' | 'project';
41
52
  type Category = 'seo' | 'performance' | 'a11y' | 'maintainability';
@@ -192,6 +203,8 @@ interface HeadTagRuleOptions {
192
203
  /** Short human label, e.g. 'description'. */
193
204
  label: string;
194
205
  recommendation: string;
206
+ /** Agent-actionable remediation attached to every finding (issue #18). */
207
+ fix?: Fix;
195
208
  }
196
209
  /** Build a route-scope rule asserting the presence of a single head tag (design §11). */
197
210
  declare function headTagRule(opts: HeadTagRuleOptions): Rule;
@@ -231,6 +244,9 @@ declare function formatJsonReport(results: Result[], config: Config, meta: {
231
244
  version: string;
232
245
  }): string;
233
246
 
247
+ /** Render failing findings as an agent-actionable Markdown remediation document (issue #18). */
248
+ declare function formatAgentReport(results: Result[], config: Config): string;
249
+
234
250
  /** Drop rules disabled via config (design §6). */
235
251
  declare function selectRules(rules: Rule[], config: Config): Rule[];
236
252
  /** Apply per-rule severity overrides to results (design §6). */
@@ -252,4 +268,4 @@ interface ScoreOptions {
252
268
  /** Compute the headline score and its breakdown (design §12). */
253
269
  declare function computeScore(results: Result[], config: Config, options?: ScoreOptions): ScoreResult;
254
270
 
255
- export { type Category, type Classification, type Config, type ConsoleReportOptions, type Detection, type HeadProvider, type HeadTag, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, type Result, type Rule, type RuleContext, type RuleSetting, type Runtime, SITEMAP_SOURCE_PATHS, type Scope, type ScoreModel, type ScoreOptions, type ScoreResult, type Severity, type Summary, type TreatDynamicAs, type Value, allRules, applyRuleSeverities, classify, computeScore, defaultConfig, defaultProject, defineConfig, effectiveSeverity, formatConsoleReport, formatJsonReport, hasFailureAtOrAbove, headTagRule, isPenalized, runRules, selectRules, seo001Title, seo002Description, seo003Canonical, seo004OgImage, seo005OgTitle, seo006Robots, seo007Sitemap, seo008JsonLd, seo009HtmlLang, summarize };
271
+ export { type Category, type Classification, type Config, type ConsoleReportOptions, type Detection, type Fix, type HeadProvider, type HeadTag, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, type Result, type Rule, type RuleContext, type RuleSetting, type Runtime, SITEMAP_SOURCE_PATHS, type Scope, type ScoreModel, type ScoreOptions, type ScoreResult, type Severity, type Summary, type TreatDynamicAs, type Value, allRules, applyRuleSeverities, classify, computeScore, defaultConfig, defaultProject, defineConfig, effectiveSeverity, formatAgentReport, formatConsoleReport, formatJsonReport, hasFailureAtOrAbove, headTagRule, isPenalized, runRules, selectRules, seo001Title, seo002Description, seo003Canonical, seo004OgImage, seo005OgTitle, seo006Robots, seo007Sitemap, seo008JsonLd, seo009HtmlLang, summarize };
package/dist/index.js CHANGED
@@ -71,7 +71,12 @@ var seo001Title = {
71
71
  location: head.file,
72
72
  message: messageFor(detection),
73
73
  recommendation: "Add a <title> inside <svelte:head>, e.g. <title>{data.title}</title>, or set it via your meta component.",
74
- docsUrl: DOCS_URL
74
+ docsUrl: DOCS_URL,
75
+ fix: {
76
+ description: "Add a <title> inside <svelte:head> (a dynamic title is fine).",
77
+ snippet: "<svelte:head>\n <title>{data.title}</title>\n</svelte:head>",
78
+ lang: "svelte"
79
+ }
75
80
  };
76
81
  });
77
82
  }
@@ -102,7 +107,10 @@ function headTagRule(opts) {
102
107
  location: head.file,
103
108
  message,
104
109
  recommendation: opts.recommendation,
105
- docsUrl
110
+ docsUrl,
111
+ // Copy per finding: opts.fix is a rule-level template shared across all
112
+ // results this rule emits; a fresh object keeps findings independent.
113
+ ...opts.fix ? { fix: { ...opts.fix } } : {}
106
114
  };
107
115
  });
108
116
  }
@@ -116,7 +124,12 @@ var seo002Description = headTagRule({
116
124
  severity: "critical",
117
125
  match: (t) => t.kind === "meta" && t.name === "description",
118
126
  label: '<meta name="description">',
119
- recommendation: 'Add a <meta name="description"> in <svelte:head>, or set the description on your meta component.'
127
+ recommendation: 'Add a <meta name="description"> in <svelte:head>, or set the description on your meta component.',
128
+ fix: {
129
+ description: 'Add a <meta name="description"> inside <svelte:head>, or set description on your meta component.',
130
+ snippet: '<svelte:head>\n <meta name="description" content="A concise page summary." />\n</svelte:head>',
131
+ lang: "svelte"
132
+ }
120
133
  });
121
134
  var seo003Canonical = headTagRule({
122
135
  id: "SEO003",
@@ -124,7 +137,12 @@ var seo003Canonical = headTagRule({
124
137
  severity: "warning",
125
138
  match: (t) => t.kind === "link" && t.rel === "canonical",
126
139
  label: '<link rel="canonical">',
127
- recommendation: 'Add <link rel="canonical"> in <svelte:head>, or set the canonical prop on your meta component.'
140
+ recommendation: 'Add <link rel="canonical"> in <svelte:head>, or set the canonical prop on your meta component.',
141
+ fix: {
142
+ description: 'Add <link rel="canonical"> inside <svelte:head>, or set the canonical prop on your meta component.',
143
+ snippet: '<svelte:head>\n <link rel="canonical" href="https://example.com/this-page" />\n</svelte:head>',
144
+ lang: "svelte"
145
+ }
128
146
  });
129
147
  var seo004OgImage = headTagRule({
130
148
  id: "SEO004",
@@ -132,7 +150,12 @@ var seo004OgImage = headTagRule({
132
150
  severity: "warning",
133
151
  match: (t) => t.kind === "meta" && t.property === "og:image",
134
152
  label: '<meta property="og:image">',
135
- recommendation: 'Add <meta property="og:image">, or set openGraph.images on your meta component.'
153
+ recommendation: 'Add <meta property="og:image">, or set openGraph.images on your meta component.',
154
+ fix: {
155
+ description: 'Add <meta property="og:image">, or set openGraph.images on your meta component.',
156
+ snippet: '<svelte:head>\n <meta property="og:image" content="https://example.com/og.png" />\n</svelte:head>',
157
+ lang: "svelte"
158
+ }
136
159
  });
137
160
  var seo005OgTitle = headTagRule({
138
161
  id: "SEO005",
@@ -140,7 +163,12 @@ var seo005OgTitle = headTagRule({
140
163
  severity: "warning",
141
164
  match: (t) => t.kind === "meta" && t.property === "og:title",
142
165
  label: '<meta property="og:title">',
143
- recommendation: 'Add <meta property="og:title">, or set openGraph.title on your meta component.'
166
+ recommendation: 'Add <meta property="og:title">, or set openGraph.title on your meta component.',
167
+ fix: {
168
+ description: 'Add <meta property="og:title">, or set openGraph.title on your meta component.',
169
+ snippet: '<svelte:head>\n <meta property="og:title" content="Page title" />\n</svelte:head>',
170
+ lang: "svelte"
171
+ }
144
172
  });
145
173
  var seo008JsonLd = headTagRule({
146
174
  id: "SEO008",
@@ -148,7 +176,15 @@ var seo008JsonLd = headTagRule({
148
176
  severity: "info",
149
177
  match: (t) => t.kind === "jsonld",
150
178
  label: 'JSON-LD (<script type="application/ld+json">)',
151
- recommendation: "Add JSON-LD structured data, e.g. via <svelte:head> or a JsonLd component."
179
+ recommendation: "Add JSON-LD structured data, e.g. via <svelte:head> or a JsonLd component.",
180
+ fix: {
181
+ // Svelte ships <script> contents verbatim (the body is raw text, not Svelte
182
+ // markup), so use literal JSON here — an interpolation like {JSON.stringify(...)}
183
+ // would be emitted as that literal string and produce invalid JSON-LD.
184
+ description: "Add a JSON-LD <script> inside <svelte:head> with literal JSON (Svelte emits the script body as-is).",
185
+ snippet: '<svelte:head>\n <script type="application/ld+json">\n {\n "@context": "https://schema.org",\n "@type": "WebPage",\n "name": "Page title"\n }\n </script>\n</svelte:head>',
186
+ lang: "svelte"
187
+ }
152
188
  });
153
189
 
154
190
  // src/rules/seo/project-rules.ts
@@ -169,7 +205,12 @@ var seo006Robots = {
169
205
  detection,
170
206
  message: ctx.project.hasRobotsTxt ? "robots.txt" : "Missing robots.txt",
171
207
  recommendation: "Add static/robots.txt or a src/routes/robots.txt/+server endpoint.",
172
- docsUrl: "https://svelte-vitals.dev/rules/SEO006"
208
+ docsUrl: "https://svelte-vitals.dev/rules/SEO006",
209
+ fix: {
210
+ description: "Create static/robots.txt (or a src/routes/robots.txt/+server endpoint).",
211
+ snippet: "User-agent: *\nAllow: /\n\nSitemap: https://example.com/sitemap.xml",
212
+ lang: "text"
213
+ }
173
214
  }
174
215
  ];
175
216
  }
@@ -189,7 +230,12 @@ var seo007Sitemap = {
189
230
  detection,
190
231
  message: ctx.project.hasSitemap ? "sitemap.xml" : "Missing sitemap.xml",
191
232
  recommendation: "Add static/sitemap.xml or a src/routes/sitemap.xml/+server endpoint.",
192
- docsUrl: "https://svelte-vitals.dev/rules/SEO007"
233
+ docsUrl: "https://svelte-vitals.dev/rules/SEO007",
234
+ fix: {
235
+ description: "Create static/sitemap.xml (or a src/routes/sitemap.xml/+server endpoint).",
236
+ snippet: '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n <url><loc>https://example.com/</loc></url>\n</urlset>',
237
+ lang: "xml"
238
+ }
193
239
  }
194
240
  ];
195
241
  }
@@ -210,7 +256,12 @@ var seo009HtmlLang = {
210
256
  detection,
211
257
  message,
212
258
  recommendation: 'Set <html lang="..."> in src/app.html.',
213
- docsUrl: "https://svelte-vitals.dev/rules/SEO009"
259
+ docsUrl: "https://svelte-vitals.dev/rules/SEO009",
260
+ fix: {
261
+ description: "Set the lang attribute on <html> in src/app.html.",
262
+ snippet: '<html lang="en">',
263
+ lang: "html"
264
+ }
214
265
  }
215
266
  ];
216
267
  }
@@ -380,7 +431,8 @@ function issueOf(result) {
380
431
  title: result.message,
381
432
  detection: result.detection,
382
433
  location: result.location,
383
- recommendation: result.recommendation
434
+ recommendation: result.recommendation,
435
+ ...result.fix ? { fix: result.fix } : {}
384
436
  };
385
437
  }
386
438
  function formatJsonReport(results, config, meta) {
@@ -401,6 +453,52 @@ function formatJsonReport(results, config, meta) {
401
453
  return JSON.stringify({ version: meta.version, score, scoreModel, summary, routes, siteIssues }, null, 2);
402
454
  }
403
455
 
456
+ // src/reporter/agent.ts
457
+ var SEVERITY_RANK = { critical: 0, warning: 1, info: 2 };
458
+ function mdTags(text) {
459
+ return text.replace(/<[^>]+>/g, (tag) => `\`${tag}\``);
460
+ }
461
+ function formatAgentReport(results, config) {
462
+ const failing = results.filter((r) => classify(r, config) === "fail");
463
+ const lines = ["# svelte-vitals \u2014 SEO fixes", ""];
464
+ if (failing.length === 0) {
465
+ lines.push("No issues to fix.", "");
466
+ return lines.join("\n").replace(/\n+$/, "\n");
467
+ }
468
+ lines.push(
469
+ `${failing.length} issue(s) to fix, ordered most-severe first. Fix critical issues first; warning and info items improve SEO but do not fail the default build. Apply each fix below, then re-run \`svelte-vitals\` (or the build) to confirm each rule passes.`,
470
+ ""
471
+ );
472
+ const groups = /* @__PURE__ */ new Map();
473
+ for (const r of failing) {
474
+ const key = r.location ?? r.route ?? "(project)";
475
+ if (!groups.has(key)) groups.set(key, []);
476
+ groups.get(key).push(r);
477
+ }
478
+ const groupSeverity = (rs) => Math.min(...rs.map((r) => SEVERITY_RANK[effectiveSeverity(r, config)]));
479
+ const orderedGroups = [...groups.entries()].sort(
480
+ (a, b) => groupSeverity(a[1]) - groupSeverity(b[1]) || a[0].localeCompare(b[0])
481
+ );
482
+ for (const [loc, rs] of orderedGroups) {
483
+ rs.sort(
484
+ (x, y) => SEVERITY_RANK[effectiveSeverity(x, config)] - SEVERITY_RANK[effectiveSeverity(y, config)] || x.id.localeCompare(y.id)
485
+ );
486
+ lines.push(`## ${loc}`, "");
487
+ for (const r of rs) {
488
+ lines.push(`### ${r.id} \xB7 ${mdTags(r.message)} (${effectiveSeverity(r, config)})`);
489
+ if (r.fix) {
490
+ lines.push(`- Fix: ${mdTags(r.fix.description)}`);
491
+ if (r.fix.snippet) lines.push("", "```" + (r.fix.lang ?? "svelte"), r.fix.snippet, "```");
492
+ } else if (r.recommendation) {
493
+ lines.push(`- Fix: ${mdTags(r.recommendation)}`);
494
+ }
495
+ if (r.docsUrl) lines.push(`- Docs: ${r.docsUrl}`);
496
+ lines.push(`- Accept: re-run svelte-vitals; ${r.id} passes${r.route ? ` for ${r.route}` : ""}.`, "");
497
+ }
498
+ }
499
+ return lines.join("\n").replace(/\n+$/, "\n");
500
+ }
501
+
404
502
  // src/config-apply.ts
405
503
  function selectRules(rules, config) {
406
504
  return rules.filter((rule) => config.rules[rule.id] !== "off");
@@ -422,6 +520,7 @@ export {
422
520
  defaultProject,
423
521
  defineConfig,
424
522
  effectiveSeverity,
523
+ formatAgentReport,
425
524
  formatConsoleReport,
426
525
  formatJsonReport,
427
526
  hasFailureAtOrAbove,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/core",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Shared, runtime-agnostic core for svelte-vitals (types, rule engine, scorer, reporter).",
5
5
  "type": "module",
6
6
  "license": "MIT",