nfunc-mcp 0.3.0 → 0.5.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.
Files changed (69) hide show
  1. package/README.md +121 -379
  2. package/dist/index.js +4 -0
  3. package/dist/index.js.map +1 -1
  4. package/dist/mappers/a11yDedupe.js +38 -7
  5. package/dist/mappers/a11yDedupe.js.map +1 -1
  6. package/dist/mappers/defectFormatter.d.ts +9 -1
  7. package/dist/mappers/defectFormatter.js +53 -13
  8. package/dist/mappers/defectFormatter.js.map +1 -1
  9. package/dist/mappers/labFieldComparator.d.ts +62 -0
  10. package/dist/mappers/labFieldComparator.js +134 -0
  11. package/dist/mappers/labFieldComparator.js.map +1 -0
  12. package/dist/mappers/priorityMapper.d.ts +42 -0
  13. package/dist/mappers/priorityMapper.js +58 -0
  14. package/dist/mappers/priorityMapper.js.map +1 -1
  15. package/dist/mappers/psiAggregator.d.ts +130 -0
  16. package/dist/mappers/psiAggregator.js +293 -0
  17. package/dist/mappers/psiAggregator.js.map +1 -0
  18. package/dist/mappers/runComparator.d.ts +85 -0
  19. package/dist/mappers/runComparator.js +165 -0
  20. package/dist/mappers/runComparator.js.map +1 -0
  21. package/dist/mappers/wcagLevels.d.ts +73 -0
  22. package/dist/mappers/wcagLevels.js +320 -0
  23. package/dist/mappers/wcagLevels.js.map +1 -0
  24. package/dist/mappers/webVitalsMapper.d.ts +52 -0
  25. package/dist/mappers/webVitalsMapper.js +131 -0
  26. package/dist/mappers/webVitalsMapper.js.map +1 -0
  27. package/dist/tools/accessibility.d.ts +1 -0
  28. package/dist/tools/accessibility.js +488 -63
  29. package/dist/tools/accessibility.js.map +1 -1
  30. package/dist/tools/lighthouse.js +370 -102
  31. package/dist/tools/lighthouse.js.map +1 -1
  32. package/dist/tools/performanceAudit.d.ts +2 -0
  33. package/dist/tools/performanceAudit.js +446 -0
  34. package/dist/tools/performanceAudit.js.map +1 -0
  35. package/dist/tools/performanceAuditPlan.d.ts +2 -0
  36. package/dist/tools/performanceAuditPlan.js +438 -0
  37. package/dist/tools/performanceAuditPlan.js.map +1 -0
  38. package/dist/utils/batchState.d.ts +75 -0
  39. package/dist/utils/batchState.js +128 -0
  40. package/dist/utils/batchState.js.map +1 -0
  41. package/dist/utils/csvReader.d.ts +20 -0
  42. package/dist/utils/csvReader.js +172 -0
  43. package/dist/utils/csvReader.js.map +1 -0
  44. package/dist/utils/httpClient.d.ts +84 -0
  45. package/dist/utils/httpClient.js +171 -0
  46. package/dist/utils/httpClient.js.map +1 -0
  47. package/dist/utils/outputParsers.js +26 -30
  48. package/dist/utils/outputParsers.js.map +1 -1
  49. package/dist/utils/psiAuth.d.ts +26 -0
  50. package/dist/utils/psiAuth.js +36 -0
  51. package/dist/utils/psiAuth.js.map +1 -0
  52. package/dist/utils/psiParser.d.ts +135 -0
  53. package/dist/utils/psiParser.js +200 -0
  54. package/dist/utils/psiParser.js.map +1 -0
  55. package/dist/utils/publicUrl.d.ts +17 -0
  56. package/dist/utils/publicUrl.js +115 -0
  57. package/dist/utils/publicUrl.js.map +1 -0
  58. package/dist/utils/sitemapReader.d.ts +27 -0
  59. package/dist/utils/sitemapReader.js +272 -0
  60. package/dist/utils/sitemapReader.js.map +1 -0
  61. package/dist/utils/urlClassifier.d.ts +45 -0
  62. package/dist/utils/urlClassifier.js +267 -0
  63. package/dist/utils/urlClassifier.js.map +1 -0
  64. package/dist/utils/urlInput.d.ts +30 -0
  65. package/dist/utils/urlInput.js +130 -0
  66. package/dist/utils/urlInput.js.map +1 -0
  67. package/docs/manual.md +769 -0
  68. package/docs/psi-report-spec.md +174 -0
  69. package/package.json +13 -3
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Core Web Vitals field data → priorities and defect prose.
3
+ *
4
+ * The lab side of a PSI response goes through the existing
5
+ * `formatLighthouseFinding` path unchanged. This module handles the CrUX half,
6
+ * where the inputs are 75th-percentile measurements from real users rather
7
+ * than audit scores, so neither the impact-weight mapping nor the WCAG
8
+ * technique table applies.
9
+ *
10
+ * Field findings are written to read differently from lab findings on purpose.
11
+ * A lab finding says the page did something under simulation; a field finding
12
+ * says a measurable share of real people already experienced it. That
13
+ * distinction is the whole reason to call PSI, and it should survive into the
14
+ * defect ticket.
15
+ */
16
+ /** Google's official p75 boundaries. */
17
+ const VITALS = {
18
+ lcp: { label: "Largest Contentful Paint", good: 2500, needsImprovement: 4000, isCoreVital: true },
19
+ inp: { label: "Interaction to Next Paint", good: 200, needsImprovement: 500, isCoreVital: true },
20
+ cls: { label: "Cumulative Layout Shift", good: 0.1, needsImprovement: 0.25, isCoreVital: true },
21
+ fcp: { label: "First Contentful Paint", good: 1800, needsImprovement: 3000, isCoreVital: false },
22
+ ttfb: { label: "Time to First Byte", good: 800, needsImprovement: 1800, isCoreVital: false },
23
+ };
24
+ export function vitalLabel(vital) {
25
+ return VITALS[vital].label;
26
+ }
27
+ /**
28
+ * LCP, INP and CLS gate a release and affect ranking; FCP and TTFB explain
29
+ * them. Exported because the priority cap has to hold everywhere a priority is
30
+ * decided, not just where one is first assigned.
31
+ */
32
+ export function isCoreVital(vital) {
33
+ return VITALS[vital].isCoreVital;
34
+ }
35
+ /**
36
+ * Rate a p75 value against Google's thresholds.
37
+ *
38
+ * PSI also returns its own FAST/AVERAGE/SLOW `category` per metric, and the two
39
+ * agree in practice. We classify from the published thresholds anyway, so that
40
+ * the boundary a finding was raised at is a documented number in this file
41
+ * rather than a verdict from an opaque field — and so origin-level and
42
+ * URL-level metrics are graded identically.
43
+ */
44
+ export function classifyVital(vital, p75) {
45
+ const spec = VITALS[vital];
46
+ if (p75 <= spec.good)
47
+ return "good";
48
+ if (p75 <= spec.needsImprovement)
49
+ return "needs-improvement";
50
+ return "poor";
51
+ }
52
+ /**
53
+ * Poor → P1, needs improvement → P2, good → no finding (never report a passing
54
+ * check). Non-core diagnostics cap at P2.
55
+ */
56
+ export function fieldVitalToPriority(vital, p75) {
57
+ const rating = classifyVital(vital, p75);
58
+ if (rating === "good")
59
+ return null;
60
+ if (rating === "needs-improvement")
61
+ return "P2";
62
+ return VITALS[vital].isCoreVital ? "P1" : "P2";
63
+ }
64
+ /** Human-readable measurement. CLS is unitless; everything else is milliseconds. */
65
+ export function formatVitalValue(vital, value) {
66
+ if (vital === "cls")
67
+ return value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
68
+ return value >= 1000 ? `${(value / 1000).toFixed(1)} s` : `${Math.round(value)} ms`;
69
+ }
70
+ /** "7 in 10 users" reads more concretely in a ticket than "0.7019". */
71
+ function shareOfUsers(proportion) {
72
+ const pct = Math.round(proportion * 100);
73
+ return `${pct}% of real users`;
74
+ }
75
+ /**
76
+ * Defect prose for each vital, in the register `defectFormatter.ts` established:
77
+ * what the user experiences, not what the metric is called.
78
+ *
79
+ * Every template states the share of real users in the poor bucket. A p75 alone
80
+ * invites the reply "that's just the tail" — naming the proportion answers it
81
+ * before it is asked, and the number is already in the response.
82
+ */
83
+ const FIELD_DESCRIPTIONS = {
84
+ lcp: (_m, value, poor) => `Real users wait ${value} for the main page content to appear (75th percentile, trailing 28 days). ${poor} experience a load slow enough to be rated poor, well past the point where visitors begin abandoning the page.`,
85
+ inp: (_m, value, poor) => `The page takes ${value} to respond visibly after a real user taps or clicks (75th percentile, trailing 28 days). ${poor} experience responsiveness rated poor — taps appear to do nothing, so users tap again and trigger duplicate actions.`,
86
+ cls: (_m, value, poor) => `Real users see the layout shift by ${value} while the page loads (75th percentile, trailing 28 days). ${poor} experience shifting rated poor, which causes mis-taps on the wrong control and loss of reading position.`,
87
+ fcp: (_m, value, poor) => `Real users stare at a blank screen for ${value} before anything paints (75th percentile, trailing 28 days). ${poor} experience a first paint rated poor. This is a diagnostic for the slow Largest Contentful Paint rather than a defect to fix on its own.`,
88
+ ttfb: (_m, value, poor) => `The server takes ${value} to return the first byte for real users (75th percentile, trailing 28 days). ${poor} experience a response rated poor; every downstream resource waits on this, so it caps how fast the rest of the page can possibly be.`,
89
+ };
90
+ /**
91
+ * One field metric → a Finding, or null when real users are having a fine time.
92
+ *
93
+ * `source` reaches the evidence deliberately. An origin-level metric describes
94
+ * the whole site, not this page, and a reader deciding whether to act on the
95
+ * finding needs to know which they are looking at.
96
+ */
97
+ export function formatFieldFinding(vital, metric) {
98
+ const priority = fieldVitalToPriority(vital, metric.p75);
99
+ if (!priority)
100
+ return null;
101
+ const value = formatVitalValue(vital, metric.p75);
102
+ const poor = shareOfUsers(metric.distribution.poor);
103
+ const spec = VITALS[vital];
104
+ const scope = metric.source === "origin" ? " (site-wide data)" : "";
105
+ return {
106
+ priority,
107
+ title: `${spec.label} is ${classifyVital(vital, metric.p75) === "poor" ? "poor" : "below target"} for real users${scope}`,
108
+ description: FIELD_DESCRIPTIONS[vital](metric, value, poor) +
109
+ (metric.source === "origin"
110
+ ? " This URL has too little traffic for its own field data, so these figures describe the whole origin and may not reflect this page."
111
+ : ""),
112
+ evidence: {
113
+ audit_id: `crux.${vital}`,
114
+ value,
115
+ threshold: formatVitalValue(vital, spec.good),
116
+ field_source: metric.source,
117
+ users_affected_pct: Math.round(metric.distribution.poor * 100),
118
+ },
119
+ };
120
+ }
121
+ /** Every failing field metric in a parsed CrUX block, unsorted. */
122
+ export function formatFieldFindings(metrics) {
123
+ const findings = [];
124
+ for (const [vital, metric] of Object.entries(metrics)) {
125
+ const finding = formatFieldFinding(vital, metric);
126
+ if (finding)
127
+ findings.push(finding);
128
+ }
129
+ return findings;
130
+ }
131
+ //# sourceMappingURL=webVitalsMapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webVitalsMapper.js","sourceRoot":"","sources":["../../src/mappers/webVitalsMapper.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,3 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export type A11yFormFactor = "desktop" | "mobile";
2
3
  export declare function registerAccessibilityTool(server: McpServer): void;