@svelte-vitals/core 0.36.0 → 0.37.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
@@ -956,6 +956,14 @@ interface RuleContext {
956
956
  sourceFiles?: string[];
957
957
  project: Project;
958
958
  config: Config;
959
+ /**
960
+ * Report per-declaration counts of places this rule examined. The engine supplies it and keys the
961
+ * result by rule id; a rule that does not call it gets no entry, which is distinct from an entry of
962
+ * zeros. Absent in contexts a caller builds directly. Silent last-write-wins: calling it more than
963
+ * once keeps only the most recent map, with no merge and no error — call it once, with the complete
964
+ * counts, at the end of `check()`.
965
+ */
966
+ recordExamined?: (counts: Record<string, number>) => void;
959
967
  }
960
968
  interface Rule {
961
969
  id: string;
@@ -995,7 +1003,10 @@ declare function isPenalized(detection: Detection, treatDynamicAs: TreatDynamicA
995
1003
  * Rules are independent, so they run concurrently; results are flattened in
996
1004
  * rule order for stable output.
997
1005
  */
998
- declare function runRules(rules: Rule[], ctx: RuleContext): Promise<Result[]>;
1006
+ declare function runRules(rules: Rule[], ctx: RuleContext): Promise<{
1007
+ results: Result[];
1008
+ examined: Record<string, Record<string, number>>;
1009
+ }>;
999
1010
 
1000
1011
  /**
1001
1012
  * seo/title-presence — every route should resolve a non-empty <title> (design §11).
@@ -1554,15 +1565,22 @@ interface JsonReport {
1554
1565
  * and floors that sum once, so adding this map's already-floored entries and re-dividing can disagree.
1555
1566
  */
1556
1567
  inventories: Record<string, number>;
1568
+ /**
1569
+ * Per-rule, per-declaration counts of places examined. Unlike `rules`, this describes the analysis
1570
+ * rather than the report: `--diff`, `--baseline` and suppressions do not narrow it. Three states: a
1571
+ * rule that reports no counts has no entry; a rule that counts but whose configuration declares
1572
+ * nothing has an empty entry; a declaration that judged nothing has an entry of `0`.
1573
+ */
1574
+ examined?: Record<string, Record<string, number>>;
1557
1575
  }
1558
1576
  /** Build the structured JSON report object (design §7). The shape the `json` reporter emits (issue #24). */
1559
1577
  declare function buildJsonReport(results: Result[], config: Config, meta: {
1560
1578
  version: string;
1561
- }, ruleIds?: readonly string[]): JsonReport;
1579
+ }, ruleIds?: readonly string[], examined?: Record<string, Record<string, number>>): JsonReport;
1562
1580
  /** Render results as the documented JSON report string (design §7). */
1563
1581
  declare function formatJsonReport(results: Result[], config: Config, meta: {
1564
1582
  version: string;
1565
- }, ruleIds?: readonly string[]): string;
1583
+ }, ruleIds?: readonly string[], examined?: Record<string, Record<string, number>>): string;
1566
1584
 
1567
1585
  /** Render failing findings as an agent-actionable Markdown remediation document (issue #18). */
1568
1586
  declare function formatAgentReport(results: Result[], config: Config): string;
package/dist/index.js CHANGED
@@ -2444,8 +2444,11 @@ function isPenalized(detection, treatDynamicAs) {
2444
2444
 
2445
2445
  // src/engine.ts
2446
2446
  async function runRules(rules, ctx) {
2447
- const perRule = await Promise.all(rules.map((rule) => rule.check(ctx)));
2448
- return perRule.flat();
2447
+ const examined = {};
2448
+ const perRule = await Promise.all(
2449
+ rules.map((rule) => rule.check({ ...ctx, recordExamined: (counts) => void (examined[rule.id] = counts) }))
2450
+ );
2451
+ return { results: perRule.flat(), examined };
2449
2452
  }
2450
2453
 
2451
2454
  // src/rules/seo/title-presence.ts
@@ -5470,20 +5473,17 @@ var architectureReservedNamePlacement = {
5470
5473
  const emptyNames = /* @__PURE__ */ new Map();
5471
5474
  for (const map of Object.keys(globalMaps)) {
5472
5475
  for (const [name, value] of Object.entries(globalMaps[map])) {
5473
- const globs2 = globsOf(value);
5474
- if (globs2.length === 0) {
5476
+ const globs = globsOf(value);
5477
+ if (globs.length === 0) {
5475
5478
  emptyNames.set(`${map}.${name}`, "names no position at all");
5476
5479
  continue;
5477
5480
  }
5478
- for (const glob of globs2) globalAlternatives.set(label(map, name, glob), { map, glob });
5481
+ for (const glob of globs) globalAlternatives.set(label(map, name, glob), { map, glob });
5479
5482
  }
5480
5483
  }
5481
5484
  const usedAlternatives = /* @__PURE__ */ new Set();
5482
- const excludedDirs = [];
5483
- const nonUnitParents = {
5484
- capitalisedUnitPlacements: [],
5485
- anyCaseUnitPlacements: []
5486
- };
5485
+ const examinedCounts = {};
5486
+ for (const key of globalAlternatives.keys()) examinedCounts[key] = 0;
5487
5487
  const allDirs = [...dirs].sort();
5488
5488
  for (const dir of allDirs) {
5489
5489
  const o = resolveRuleOptions(ID7, OPTIONS6, ctx.config, { route: dir, file: dir }, compiledOverrides);
@@ -5506,17 +5506,24 @@ var architectureReservedNamePlacement = {
5506
5506
  const parent = parentOf(dir);
5507
5507
  if (parent === void 0) continue;
5508
5508
  if (isExcluded(dir, ancestorDirs2(dir), excluded)) {
5509
- if (isExcluded(parent, ancestorDirs2(parent), excluded)) excludedDirs.push(parent);
5510
5509
  continue;
5511
5510
  }
5511
+ const judged = /* @__PURE__ */ new Set();
5512
+ const resolvedValues = [
5513
+ ["placements", placements[name]],
5514
+ ["capitalisedUnitPlacements", capUnits[name]],
5515
+ ["anyCaseUnitPlacements", anyUnits[name]]
5516
+ ];
5517
+ for (const [map, value] of resolvedValues) {
5518
+ if (value === void 0) continue;
5519
+ for (const glob of globsOf(value)) judged.add(label(map, name, glob));
5520
+ }
5521
+ for (const key of judged) if (globalAlternatives.has(key)) examinedCounts[key] = (examinedCounts[key] ?? 0) + 1;
5512
5522
  const record = (map, value, qualifies) => {
5513
5523
  if (value === void 0) return false;
5514
5524
  const { matched } = matchKeys(parent, compile(globsOf(value), true));
5515
5525
  if (matched.length === 0) return false;
5516
- if (!qualifies) {
5517
- if (map !== "placements") nonUnitParents[map].push(parent);
5518
- return false;
5519
- }
5526
+ if (!qualifies) return false;
5520
5527
  for (const glob of matched) usedAlternatives.add(label(map, name, glob));
5521
5528
  return true;
5522
5529
  };
@@ -5544,23 +5551,27 @@ var architectureReservedNamePlacement = {
5544
5551
  const notes = new Map(emptyNames);
5545
5552
  const unusedLabels = [...globalAlternatives.keys()].filter((k) => !usedAlternatives.has(k));
5546
5553
  const globOf = (key) => globalAlternatives.get(key)?.glob;
5547
- for (const map of ["capitalisedUnitPlacements", "anyCaseUnitPlacements"]) {
5548
- const inMap = unusedLabels.filter((k) => globalAlternatives.get(k)?.map === map);
5549
- const hit = keysMatchingAny(inMap.map(globOf), nonUnitParents[map], compile);
5550
- for (const k of inMap) {
5551
- if (hit.has(globOf(k))) notes.set(k, "matched directories but never a unit");
5552
- }
5553
- }
5554
5554
  const stillUnused = unusedLabels.filter((k) => !notes.has(k));
5555
- const globs = [...new Set(stillUnused.map(globOf))];
5556
- const reasons = classifyUnusedKeys(globs, excludedDirs, compile);
5557
- const reachable = keysMatchingAny(globs, allDirs, compile);
5558
- for (const k of stillUnused) {
5559
- const glob = globOf(k);
5560
- if (reasons.get(glob) === "only-excluded") {
5561
- notes.set(k, "matched only excluded directories");
5562
- } else if (!reachable.has(glob)) {
5563
- notes.set(k, "matched no directory");
5555
+ if (stillUnused.length > 0) {
5556
+ const globalExcluded = compile(listOption(globalOptions, "exclude"));
5557
+ const liveDirs = allDirs.filter((d) => !isExcluded(d, ancestorDirs2(d), globalExcluded));
5558
+ const liveUnits = {
5559
+ capitalisedUnitPlacements: liveDirs.filter((d) => isUnitDir(d, filesIn)),
5560
+ anyCaseUnitPlacements: liveDirs.filter((d) => isAnyCaseUnitDir(d, filesIn))
5561
+ };
5562
+ const globs = [...new Set(stillUnused.map(globOf))];
5563
+ const reachesAny = keysMatchingAny(globs, allDirs, compile);
5564
+ const reachesLive = keysMatchingAny(globs, liveDirs, compile);
5565
+ for (const k of stillUnused) {
5566
+ const glob = globOf(k);
5567
+ if (!reachesAny.has(glob)) notes.set(k, "matched no directory");
5568
+ else if (!reachesLive.has(glob)) notes.set(k, "matched only excluded directories");
5569
+ }
5570
+ for (const map of ["capitalisedUnitPlacements", "anyCaseUnitPlacements"]) {
5571
+ const inMap = stillUnused.filter((k) => !notes.has(k) && globalAlternatives.get(k)?.map === map);
5572
+ const inMapGlobs = [...new Set(inMap.map(globOf))];
5573
+ const reachesUnit = keysMatchingAny(inMapGlobs, liveUnits[map], compile);
5574
+ for (const k of inMap) if (!reachesUnit.has(globOf(k))) notes.set(k, "reaches no unit");
5564
5575
  }
5565
5576
  }
5566
5577
  const reported = [...notes.keys()].sort();
@@ -5576,6 +5587,7 @@ var architectureReservedNamePlacement = {
5576
5587
  docsUrl: docsUrl11
5577
5588
  });
5578
5589
  }
5590
+ ctx.recordExamined?.(examinedCounts);
5579
5591
  return out;
5580
5592
  }
5581
5593
  };
@@ -6263,7 +6275,7 @@ function ruleEvidence(results, config, ruleIds) {
6263
6275
  }
6264
6276
  return out;
6265
6277
  }
6266
- function buildJsonReport(results, config, meta, ruleIds) {
6278
+ function buildJsonReport(results, config, meta, ruleIds, examined) {
6267
6279
  const { health, categories: byCat, weights } = computeHealth(results, config);
6268
6280
  const summary = summarize(results, config);
6269
6281
  const rules = ruleEvidence(results, config, ruleIds);
@@ -6293,10 +6305,21 @@ function buildJsonReport(results, config, meta, ruleIds) {
6293
6305
  const inventories = Object.fromEntries(
6294
6306
  [...buildInventory(config)].map(([pair, weight]) => [pair, Math.max(weight, INVENTORY_FLOOR)])
6295
6307
  );
6296
- return { version: meta.version, score: health, weights, categories, summary, rules, routes, siteIssues, inventories };
6308
+ return {
6309
+ version: meta.version,
6310
+ score: health,
6311
+ weights,
6312
+ categories,
6313
+ summary,
6314
+ rules,
6315
+ routes,
6316
+ siteIssues,
6317
+ inventories,
6318
+ ...examined && Object.keys(examined).length > 0 ? { examined } : {}
6319
+ };
6297
6320
  }
6298
- function formatJsonReport(results, config, meta, ruleIds) {
6299
- return JSON.stringify(buildJsonReport(results, config, meta, ruleIds), null, 2);
6321
+ function formatJsonReport(results, config, meta, ruleIds, examined) {
6322
+ return JSON.stringify(buildJsonReport(results, config, meta, ruleIds, examined), null, 2);
6300
6323
  }
6301
6324
 
6302
6325
  // src/reporter/agent.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svelte-vitals/core",
3
- "version": "0.36.0",
3
+ "version": "0.37.0",
4
4
  "description": "Shared, runtime-agnostic core for svelte-vitals (types, rule engine, scorer, reporter).",
5
5
  "type": "module",
6
6
  "license": "MIT",