@svelte-vitals/core 0.31.0 → 0.31.1

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
@@ -946,9 +946,9 @@ interface Rule {
946
946
  severity: Severity;
947
947
  /** 'route' = evaluated per route, 'project' = site-wide (design §10, §12). */
948
948
  scope: Scope;
949
- /** Why this rule matters — one or two sentences, surfaced by explain_rule (issue #24). */
949
+ /** Why this rule matters — one or two sentences, surfaced by `svelte-vitals explain` (issue #24). */
950
950
  rationale: string;
951
- /** Canonical remediation template, shared by findings and explain_rule (issue #24). */
951
+ /** Canonical remediation template, shared by findings and `svelte-vitals explain` (issue #24). */
952
952
  fix?: Fix;
953
953
  /** Configurable options for this rule; absent means the rule takes none. */
954
954
  options?: RuleOptionsSpec;
@@ -1290,7 +1290,7 @@ declare const performanceStateRaw: Rule;
1290
1290
 
1291
1291
  declare const allRules: Rule[];
1292
1292
 
1293
- /** One configurable option of a rule, flattened for explain_rule's consumers. */
1293
+ /** One configurable option of a rule, flattened for `svelte-vitals explain`'s output. */
1294
1294
  interface RuleOptionInfo {
1295
1295
  name: string;
1296
1296
  /** `integer` replaces the default; `string-list`/`string-map` are ADDED to it. */
@@ -1314,7 +1314,7 @@ interface RuleInfo {
1314
1314
  */
1315
1315
  options?: RuleOptionInfo[];
1316
1316
  }
1317
- /** Look up a rule's static metadata for the MCP explain_rule tool (issue #24). Rule ids are matched exactly (case-sensitive, e.g. "seo/ssr-disabled"). */
1317
+ /** Look up a rule's static metadata, as `svelte-vitals explain` renders it (issue #24). Rule ids are matched exactly (case-sensitive, e.g. "seo/ssr-disabled"). */
1318
1318
  declare function explainRule(id: string): RuleInfo | undefined;
1319
1319
 
1320
1320
  interface HeadTagRuleOptions {
@@ -1326,7 +1326,7 @@ interface HeadTagRuleOptions {
1326
1326
  /** Short human label, e.g. 'description'. */
1327
1327
  label: string;
1328
1328
  recommendation: string;
1329
- /** Why this rule matters — surfaced by explain_rule (issue #24). */
1329
+ /** Why this rule matters — surfaced by `svelte-vitals explain` (issue #24). */
1330
1330
  rationale: string;
1331
1331
  /** Agent-actionable remediation attached to every finding (issue #18). */
1332
1332
  fix?: Fix;
@@ -1432,7 +1432,14 @@ interface ScoreModel {
1432
1432
  criticalCap: number | null;
1433
1433
  }
1434
1434
  interface ScoreResult {
1435
+ /** The score as displayed: `Math.floor(rawScore)`, so 100 means the deduction was exactly zero. */
1435
1436
  score: number;
1437
+ /**
1438
+ * The same score before flooring, after `sitePenalty` and the cap, clamped to `[0, 100]`. Exposed so
1439
+ * `computeHealth` can average unrounded values and floor once — averaging the displayed scores would
1440
+ * compose two roundings and move Health by up to two points.
1441
+ */
1442
+ rawScore: number;
1436
1443
  scoreModel: ScoreModel;
1437
1444
  }
1438
1445
  interface ScoreOptions {
@@ -1482,7 +1489,7 @@ interface JsonReport {
1482
1489
  }>;
1483
1490
  siteIssues: JsonIssue[];
1484
1491
  }
1485
- /** Build the structured JSON report object (design §7). Shared by the json reporter and the MCP `analyze` tool (issue #24). */
1492
+ /** Build the structured JSON report object (design §7). The shape the `json` reporter emits (issue #24). */
1486
1493
  declare function buildJsonReport(results: Result[], config: Config, meta: {
1487
1494
  version: string;
1488
1495
  }): JsonReport;
package/dist/index.js CHANGED
@@ -4938,7 +4938,7 @@ var architectureUnitEntryFile = {
4938
4938
  category: "architecture",
4939
4939
  severity: "info",
4940
4940
  detection: { presence: "own", value: "static" },
4941
- route: expected,
4941
+ location: expected,
4942
4942
  message: "Unit entry file",
4943
4943
  recommendation: recommendation8,
4944
4944
  docsUrl: docsUrl8
@@ -5641,7 +5641,8 @@ function computeScore(results, config, options = {}) {
5641
5641
  routeScores.set(route, routeScores.get(route) - deduction);
5642
5642
  }
5643
5643
  const scores = [...routeScores.values()].map(clamp);
5644
- const routeAverage = scores.length ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 100;
5644
+ const rawRouteAverage = scores.length ? scores.reduce((a, b) => a + b, 0) / scores.length : 100;
5645
+ const routeAverage = Math.floor(rawRouteAverage);
5645
5646
  const projectRuleMax = /* @__PURE__ */ new Map();
5646
5647
  for (const r of projectResults) {
5647
5648
  if (!isPenalized(r.detection, config.treatDynamicAs)) continue;
@@ -5653,11 +5654,11 @@ function computeScore(results, config, options = {}) {
5653
5654
  let sitePenalty = 0;
5654
5655
  for (const deduction of projectRuleMax.values()) sitePenalty += deduction;
5655
5656
  const applyCap = options.applyCriticalCap ?? true;
5656
- const uncapped = routeAverage - sitePenalty;
5657
- const capBinds = applyCap && anyCritical && uncapped > CRITICAL_CAP;
5657
+ const rawUncapped = rawRouteAverage - sitePenalty;
5658
+ const capBinds = applyCap && anyCritical && rawUncapped > CRITICAL_CAP;
5658
5659
  const criticalCap = capBinds ? CRITICAL_CAP : null;
5659
- const score = capBinds ? CRITICAL_CAP : uncapped;
5660
- return { score: clamp(score), scoreModel: { routeAverage, sitePenalty, criticalCap } };
5660
+ const rawScore = clamp(capBinds ? CRITICAL_CAP : rawUncapped);
5661
+ return { score: Math.floor(rawScore), rawScore, scoreModel: { routeAverage, sitePenalty, criticalCap } };
5661
5662
  }
5662
5663
  function scoresByCategory(results, config) {
5663
5664
  const byCat = /* @__PURE__ */ new Map();
@@ -5674,7 +5675,7 @@ function scoresByCategory(results, config) {
5674
5675
  function computeHealth(results, config) {
5675
5676
  const categories = scoresByCategory(results, config);
5676
5677
  const weights = {};
5677
- let weighted = 0;
5678
+ let weightedDeficit = 0;
5678
5679
  let total = 0;
5679
5680
  for (const cat of Object.keys(categories)) {
5680
5681
  const w = config.weights?.[cat] ?? 1;
@@ -5682,14 +5683,15 @@ function computeHealth(results, config) {
5682
5683
  throw new RangeError(`invalid weight for '${cat}'; expected a finite number >= 0.`);
5683
5684
  }
5684
5685
  weights[cat] = w;
5685
- weighted += categories[cat].score * w;
5686
+ weightedDeficit += (100 - categories[cat].rawScore) * w;
5686
5687
  total += w;
5687
5688
  }
5688
5689
  if (Object.keys(weights).length === 0) return { health: 100, categories, weights };
5689
5690
  if (total === 0) {
5690
5691
  throw new RangeError("Health weights sum to 0; at least one present category must have a positive weight.");
5691
5692
  }
5692
- const health = Math.round(weighted / total);
5693
+ const averageDeficit = weightedDeficit / total;
5694
+ const health = averageDeficit === 0 ? 100 : Math.min(99, Math.floor(100 - averageDeficit));
5693
5695
  return { health, categories, weights };
5694
5696
  }
5695
5697
 
@@ -5763,7 +5765,7 @@ function byRouteTree(p, results, config, verbose) {
5763
5765
  }
5764
5766
  if (!verbose && scored.length > MAX_ROUTES_BY_ROUTE) {
5765
5767
  const remaining = scored.slice(MAX_ROUTES_BY_ROUTE);
5766
- const avgScore = Math.round(remaining.reduce((sum, r) => sum + r.score, 0) / remaining.length);
5768
+ const avgScore = Math.floor(remaining.reduce((sum, r) => sum + r.score, 0) / remaining.length);
5767
5769
  lines.push(
5768
5770
  p.dim(
5769
5771
  `\u2026and ${remaining.length} more route${remaining.length > 1 ? "s" : ""} (avg score ${avgScore}) \u2014 run with --verbose to see all`
@@ -5833,8 +5835,9 @@ function formatConsoleReport(results, config, options = {}) {
5833
5835
  if (options.verbose) {
5834
5836
  for (const r of passed) {
5835
5837
  const marker = classify(r, config) === "dynamic" ? p.cyan(" \u21AF dynamic") : "";
5836
- const route = r.route ? ` ${r.route}` : "";
5837
- lines.push(`${p.green("\u2713")} ${r.id} ${r.message}${marker}${route}`);
5838
+ const where = r.location ?? r.route;
5839
+ const suffix = where ? ` ${where}` : "";
5840
+ lines.push(`${p.green("\u2713")} ${r.id} ${r.message}${marker}${suffix}`);
5838
5841
  }
5839
5842
  }
5840
5843
  lines.push("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/core",
3
- "version": "0.31.0",
3
+ "version": "0.31.1",
4
4
  "description": "Shared, runtime-agnostic core for svelte-vitals (types, rule engine, scorer, reporter).",
5
5
  "type": "module",
6
6
  "license": "MIT",