@eightmedia/flightcheck 1.0.0 → 1.1.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 (45) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/analyze/structuredReport.d.ts +9 -0
  3. package/dist/analyze/structuredReport.d.ts.map +1 -1
  4. package/dist/analyze/structuredReport.js +38 -15
  5. package/dist/analyze.js +6 -0
  6. package/dist/checks/agent-ready.d.ts.map +1 -1
  7. package/dist/checks/agent-ready.js +8 -8
  8. package/dist/checks/analyze.d.ts +4 -0
  9. package/dist/checks/analyze.d.ts.map +1 -1
  10. package/dist/checks/analyze.js +2 -0
  11. package/dist/checks/crawl.d.ts.map +1 -1
  12. package/dist/checks/crawl.js +11 -0
  13. package/dist/checks/dns.d.ts.map +1 -1
  14. package/dist/checks/dns.js +2 -2
  15. package/dist/checks/issues.d.ts +3 -1
  16. package/dist/checks/issues.d.ts.map +1 -1
  17. package/dist/checks/issues.js +2 -2
  18. package/dist/checks/redirect.d.ts.map +1 -1
  19. package/dist/checks/redirect.js +12 -12
  20. package/dist/checks/robots-txt.d.ts.map +1 -1
  21. package/dist/checks/robots-txt.js +6 -6
  22. package/dist/checks/security-headers.d.ts.map +1 -1
  23. package/dist/checks/security-headers.js +6 -6
  24. package/dist/checks/sitemap.d.ts.map +1 -1
  25. package/dist/checks/sitemap.js +6 -6
  26. package/dist/cli.js +7 -1
  27. package/dist/crawler/defaults.d.ts +2 -1
  28. package/dist/crawler/defaults.d.ts.map +1 -1
  29. package/dist/crawler/defaults.js +2 -1
  30. package/dist/crawler/runCrawler.d.ts.map +1 -1
  31. package/dist/crawler/runCrawler.js +12 -3
  32. package/dist/crawler/smartPageSelection.d.ts +77 -0
  33. package/dist/crawler/smartPageSelection.d.ts.map +1 -0
  34. package/dist/crawler/smartPageSelection.js +308 -0
  35. package/dist/version.d.ts +9 -0
  36. package/dist/version.d.ts.map +1 -0
  37. package/dist/version.js +24 -0
  38. package/docs/agent-fixes.md +5 -7
  39. package/docs/agent-ready.md +2 -2
  40. package/docs/analyze.md +19 -7
  41. package/docs/crawler.md +15 -2
  42. package/docs/index.md +2 -4
  43. package/docs/qc-checks-plan.md +5 -4
  44. package/docs/stack.md +11 -18
  45. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.1.0] — 2026-10-28
4
+
5
+ ### Added
6
+
7
+ - Findings include an optional `fix` suggestion in `*-report.json` and Markdown (`- Fix: …`)
8
+
9
+ ### Changed
10
+
11
+ - Default `--max-pages` is now **100** (was 1000)
12
+ - Within the page budget, crawl seeds are selected from **URL-tree shape** (not slug
13
+ names): home/structural pages and list indexes first, then a fair sample of leaf
14
+ URLs under parents that have many leaf children
15
+ - Discovered structural links jump the queue; leaf URLs under list parents are capped
16
+
17
+ ## [1.0.1] — 2026-10-28
18
+
19
+ ### Added
20
+
21
+ - Reports record `flightcheckVersion` and CLI `flags` in `*-report.json` and the Markdown header
22
+
3
23
  ## [1.0.0] — 2026-10-27
4
24
 
5
25
  ### Added
@@ -18,6 +18,8 @@ export type ReportFinding = {
18
18
  scope: FindingScope;
19
19
  title: string;
20
20
  message: string;
21
+ /** Concrete remediation suggestion when available. */
22
+ fix?: string;
21
23
  template?: string;
22
24
  urls?: string[];
23
25
  count?: number;
@@ -46,7 +48,12 @@ export type FlightcheckReportAnalysis = {
46
48
  reviewPages: ReportReviewPage[];
47
49
  };
48
50
  export type FlightcheckReport = {
51
+ /** Report JSON schema version (not the npm package version). */
49
52
  version: typeof FLIGHTCHECK_REPORT_VERSION;
53
+ /** npm package version of Flightcheck that produced this report. */
54
+ flightcheckVersion: string;
55
+ /** CLI argv flags used for this run (after node/script); empty when programmatic without flags. */
56
+ flags: string[];
50
57
  generatedAt: string;
51
58
  source: string;
52
59
  redirectOnly: boolean;
@@ -55,6 +62,8 @@ export type FlightcheckReport = {
55
62
  };
56
63
  export type BuildStructuredReportOptions = BuildMarkdownOptions & {
57
64
  generatedAt?: string;
65
+ flightcheckVersion?: string;
66
+ flags?: string[];
58
67
  };
59
68
  export declare function buildStructuredReport(sourcePath: string, records: ReportRecord[], tier1?: Tier1ReportData, options?: BuildStructuredReportOptions): Promise<FlightcheckReport>;
60
69
  /** Human-friendly Markdown rendered from the structured report (single source of truth). */
@@ -1 +1 @@
1
- {"version":3,"file":"structuredReport.d.ts","sourceRoot":"","sources":["../../src/analyze/structuredReport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE1E,OAAO,EAEL,KAAK,oBAAoB,EAEzB,KAAK,YAAY,EACjB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,0BAA0B,EAAG,CAAU,CAAC;AAErD,MAAM,MAAM,eAAe,GAAG,kBAAkB,CAAC;AACjD,MAAM,MAAM,eAAe,GACvB,OAAO,GACP,KAAK,GACL,MAAM,GACN,gBAAgB,GAChB,UAAU,GACV,aAAa,CAAC;AAClB,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAC;AAExE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,eAAe,CAAC;IAC1B,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;IAC/C,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,GAAG,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC9C,QAAQ,CAAC,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,WAAW,EAAE,gBAAgB,EAAE,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,OAAO,0BAA0B,CAAC;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,yBAAyB,CAAC;IACpC,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B,CAAC;AAidF,MAAM,MAAM,4BAA4B,GAAG,oBAAoB,GAAG;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,YAAY,EAAE,EACvB,KAAK,GAAE,eAAoB,EAC3B,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,iBAAiB,CAAC,CAW5B;AAgCD,4FAA4F;AAC5F,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAqFtE"}
1
+ {"version":3,"file":"structuredReport.d.ts","sourceRoot":"","sources":["../../src/analyze/structuredReport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAG1E,OAAO,EAEL,KAAK,oBAAoB,EAEzB,KAAK,YAAY,EACjB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,0BAA0B,EAAG,CAAU,CAAC;AAErD,MAAM,MAAM,eAAe,GAAG,kBAAkB,CAAC;AACjD,MAAM,MAAM,eAAe,GACvB,OAAO,GACP,KAAK,GACL,MAAM,GACN,gBAAgB,GAChB,UAAU,GACV,aAAa,CAAC;AAClB,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAC;AAExE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,eAAe,CAAC;IAC1B,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;IAC/C,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,GAAG,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC9C,QAAQ,CAAC,EAAE;QACT,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,WAAW,EAAE,gBAAgB,EAAE,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gEAAgE;IAChE,OAAO,EAAE,OAAO,0BAA0B,CAAC;IAC3C,oEAAoE;IACpE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mGAAmG;IACnG,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,yBAAyB,CAAC;IACpC,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B,CAAC;AAieF,MAAM,MAAM,4BAA4B,GAAG,oBAAoB,GAAG;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,YAAY,EAAE,EACvB,KAAK,GAAE,eAAoB,EAC3B,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,iBAAiB,CAAC,CAa5B;AAiCD,4FAA4F;AAC5F,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAuFtE"}
@@ -1,3 +1,4 @@
1
+ import { formatCliFlags, getPackageVersion } from "../version.js";
1
2
  import { buildReportAnalysis, } from "./report.js";
2
3
  export const FLIGHTCHECK_REPORT_VERSION = 1;
3
4
  const A11Y_IDS = new Set([
@@ -169,6 +170,7 @@ function findingFromCheck(issue, category, scope, extras = {}) {
169
170
  scope,
170
171
  title: humanizeId(issue.id),
171
172
  message: issue.message,
173
+ ...(issue.fix ? { fix: issue.fix } : {}),
172
174
  ...extras,
173
175
  };
174
176
  }
@@ -181,29 +183,35 @@ function pushUnique(findings, finding) {
181
183
  findings.push(finding);
182
184
  }
183
185
  function collectSeoFindings(fields, scope, count, extras = {}) {
184
- return fields.map((field) => ({
185
- id: field,
186
- severity: "error",
187
- category: "seo",
188
- scope,
189
- title: fieldTitle(field),
190
- message: fieldMessage(field, scope, extras.template),
191
- count,
192
- ...extras,
193
- }));
186
+ return fields.map((field) => {
187
+ const message = fieldMessage(field, scope, extras.template);
188
+ return {
189
+ id: field,
190
+ severity: "error",
191
+ category: "seo",
192
+ scope,
193
+ title: fieldTitle(field),
194
+ message,
195
+ fix: message,
196
+ count,
197
+ ...extras,
198
+ };
199
+ });
194
200
  }
195
201
  function collectBestPracticeFindings(keys, scope, count, extras = {}) {
196
202
  return keys
197
203
  .filter((key) => BEST_PRACTICE_IDS.has(key))
198
204
  .map((key) => {
199
205
  const copy = BEST_PRACTICE_COPY[key];
206
+ const message = copy?.message ?? `Fix ${key}.`;
200
207
  return {
201
208
  id: key,
202
209
  severity: copy?.severity ?? "review",
203
210
  category: "best-practices",
204
211
  scope,
205
212
  title: copy?.title ?? humanizeId(key),
206
- message: copy?.message ?? `Fix ${key}.`,
213
+ message,
214
+ fix: message,
207
215
  count,
208
216
  ...extras,
209
217
  };
@@ -214,13 +222,15 @@ function collectA11yFindings(keys, scope, count, extras = {}) {
214
222
  .filter((key) => A11Y_IDS.has(key))
215
223
  .map((key) => {
216
224
  const copy = A11Y_COPY[key];
225
+ const message = copy?.message ?? `Fix accessibility issue: ${key}.`;
217
226
  return {
218
227
  id: key,
219
228
  severity: "warning",
220
229
  category: "a11y",
221
230
  scope,
222
231
  title: copy?.title ?? humanizeId(key),
223
- message: copy?.message ?? `Fix accessibility issue: ${key}.`,
232
+ message,
233
+ fix: message,
224
234
  count,
225
235
  ...extras,
226
236
  };
@@ -283,6 +293,7 @@ function buildFindings(analysis, tier1) {
283
293
  scope: "page",
284
294
  title: `Exposed ${secret.provider} ${secret.type}`,
285
295
  message: `Rotate or remove this secret. Masked value: ${secret.masked} (source: ${secret.source}).`,
296
+ fix: "Remove the secret from client-side HTML/JS, rotate the credential, and load secrets only from a server-side env.",
286
297
  urls: [page.url],
287
298
  count: 1,
288
299
  });
@@ -312,13 +323,15 @@ function buildFindings(analysis, tier1) {
312
323
  if (!analysis.redirectOnly) {
313
324
  const seoById = aggregatePageFindings(analysis.pageFixes, (fix) => fix.remainingFields.map((id) => ({ id })));
314
325
  for (const [id, { urls }] of seoById) {
326
+ const message = fieldMessage(id, "page");
315
327
  findings.push({
316
328
  id,
317
329
  severity: "error",
318
330
  category: "seo",
319
331
  scope: "page",
320
332
  title: fieldTitle(id),
321
- message: fieldMessage(id, "page"),
333
+ message,
334
+ fix: message,
322
335
  urls,
323
336
  count: urls.length,
324
337
  });
@@ -326,13 +339,15 @@ function buildFindings(analysis, tier1) {
326
339
  const bestById = aggregatePageFindings(analysis.pageFixes, (fix) => fix.remainingWarningKeys.filter((key) => BEST_PRACTICE_IDS.has(key)).map((id) => ({ id })));
327
340
  for (const [id, { urls }] of bestById) {
328
341
  const copy = BEST_PRACTICE_COPY[id];
342
+ const message = copy?.message ?? `Fix ${id}.`;
329
343
  findings.push({
330
344
  id,
331
345
  severity: copy?.severity ?? "review",
332
346
  category: "best-practices",
333
347
  scope: "page",
334
348
  title: copy?.title ?? humanizeId(id),
335
- message: copy?.message ?? `Fix ${id}.`,
349
+ message,
350
+ fix: message,
336
351
  urls,
337
352
  count: urls.length,
338
353
  });
@@ -340,13 +355,15 @@ function buildFindings(analysis, tier1) {
340
355
  const a11yById = aggregatePageFindings(analysis.pageFixes, (fix) => fix.remainingA11yIssues.map((issue) => ({ id: issue.id, message: issue.message })));
341
356
  for (const [id, { urls, message }] of a11yById) {
342
357
  const copy = A11Y_COPY[id];
358
+ const resolved = message ?? copy?.message ?? `Fix accessibility issue: ${id}.`;
343
359
  findings.push({
344
360
  id,
345
361
  severity: "warning",
346
362
  category: "a11y",
347
363
  scope: "page",
348
364
  title: copy?.title ?? humanizeId(id),
349
- message: message ?? copy?.message ?? `Fix accessibility issue: ${id}.`,
365
+ message: resolved,
366
+ fix: copy?.message ?? resolved,
350
367
  urls,
351
368
  count: urls.length,
352
369
  });
@@ -409,6 +426,8 @@ export async function buildStructuredReport(sourcePath, records, tier1 = {}, opt
409
426
  const findings = buildFindings(analysis, tier1);
410
427
  return {
411
428
  version: FLIGHTCHECK_REPORT_VERSION,
429
+ flightcheckVersion: options.flightcheckVersion ?? getPackageVersion(),
430
+ flags: options.flags ?? [],
412
431
  generatedAt: options.generatedAt ?? new Date().toISOString(),
413
432
  source: sourcePath,
414
433
  redirectOnly: analysis.redirectOnly,
@@ -436,6 +455,8 @@ function formatFindingMarkdown(finding) {
436
455
  const lines = [`#### ${finding.title}`, "", finding.message, "", `- Where: ${scopeLabel(finding)}`];
437
456
  if (finding.category)
438
457
  lines.push(`- Category: ${finding.category}`);
458
+ if (finding.fix)
459
+ lines.push(`- Fix: ${finding.fix}`);
439
460
  if (finding.urls && finding.urls.length > 1) {
440
461
  lines.push("- Pages:");
441
462
  for (const url of finding.urls.slice(0, 20)) {
@@ -457,6 +478,8 @@ export function renderReportMarkdown(report) {
457
478
  "# Flightcheck report",
458
479
  "",
459
480
  `Generated: ${report.generatedAt}`,
481
+ `Flightcheck: ${report.flightcheckVersion}`,
482
+ `Flags: \`${formatCliFlags(report.flags)}\``,
460
483
  `Source: \`${report.source}\``,
461
484
  "",
462
485
  "## At a glance",
package/dist/analyze.js CHANGED
@@ -3,15 +3,19 @@ import { basename, resolve } from "node:path";
3
3
  import { Command } from "commander";
4
4
  import { runAnalyzeCheck } from "./checks/analyze.js";
5
5
  import { defaultReportPathForInput } from "./crawler/outputPaths.js";
6
+ import { cliFlagsFromArgv, getPackageVersion } from "./version.js";
6
7
  async function main() {
7
8
  const sanitizedArgv = [...process.argv];
8
9
  const separatorIndex = sanitizedArgv.indexOf("--");
9
10
  if (separatorIndex >= 2) {
10
11
  sanitizedArgv.splice(separatorIndex, 1);
11
12
  }
13
+ const runFlags = cliFlagsFromArgv(sanitizedArgv);
14
+ const flightcheckVersion = getPackageVersion();
12
15
  const program = new Command();
13
16
  program
14
17
  .name("flightcheck-analyze")
18
+ .version(flightcheckVersion)
15
19
  .description("Analyze crawl JSON and generate structured JSON + Markdown reports.")
16
20
  .argument("<input>", "Input JSON report file path")
17
21
  .option("--output <file>", "Output Markdown file path")
@@ -22,6 +26,8 @@ async function main() {
22
26
  inputPath,
23
27
  reportPath: resolve(options.output ?? defaultReportPathForInput(inputPath)),
24
28
  errorsOnly: options.errorsOnly,
29
+ flightcheckVersion,
30
+ flags: runFlags,
25
31
  });
26
32
  process.stdout.write(`${JSON.stringify({
27
33
  message: "Report generated",
@@ -1 +1 @@
1
- {"version":3,"file":"agent-ready.d.ts","sourceRoot":"","sources":["../../src/checks/agent-ready.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAGrD,6FAA6F;AAC7F,eAAO,MAAM,kBAAkB,8LAarB,CAAC;AAEX,4EAA4E;AAC5E,eAAO,MAAM,eAAe,wEAAyE,CAAC;AAEtG,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,iBAAiB,EAAE,OAAO,CAAC;QAC3B,cAAc,EAAE,MAAM,EAAE,CAAC;QACzB,eAAe,EAAE,MAAM,EAAE,CAAC;KAC3B,CAAC;IACF,OAAO,EAAE;QACP,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,QAAQ,EAAE,OAAO,CAAC;KACnB,CAAC;IACF,mBAAmB,EAAE;QACnB,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,gBAAgB,EAAE,OAAO,CAAC;KAC3B,CAAC;IACF,WAAW,EAAE;QACX,SAAS,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;IACF,SAAS,EAAE;QACT,aAAa,EAAE,OAAO,CAAC;QACvB,WAAW,EAAE,OAAO,CAAC;QACrB,UAAU,EAAE,OAAO,CAAC;QACpB,GAAG,EAAE,OAAO,CAAC;QACb,YAAY,EAAE,OAAO,CAAC;KACvB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,OAAO,CAAC,EAAE,qBAAqB,CAAC;IAChC,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,SAAS,CAAC,EAAE;QACV,aAAa,CAAC,EAAE,qBAAqB,CAAC;QACtC,WAAW,CAAC,EAAE,qBAAqB,CAAC;QACpC,UAAU,CAAC,EAAE,qBAAqB,CAAC;QACnC,GAAG,CAAC,EAAE,qBAAqB,CAAC;QAC5B,YAAY,CAAC,EAAE,qBAAqB,CAAC;KACtC,CAAC;CACH,CAAC;AAoDF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,qBAAqB,CAoItF;AAmCD,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA+C7G"}
1
+ {"version":3,"file":"agent-ready.d.ts","sourceRoot":"","sources":["../../src/checks/agent-ready.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAGrD,6FAA6F;AAC7F,eAAO,MAAM,kBAAkB,8LAarB,CAAC;AAEX,4EAA4E;AAC5E,eAAO,MAAM,eAAe,wEAAyE,CAAC;AAEtG,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,iBAAiB,EAAE,OAAO,CAAC;QAC3B,cAAc,EAAE,MAAM,EAAE,CAAC;QACzB,eAAe,EAAE,MAAM,EAAE,CAAC;KAC3B,CAAC;IACF,OAAO,EAAE;QACP,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,QAAQ,EAAE,OAAO,CAAC;KACnB,CAAC;IACF,mBAAmB,EAAE;QACnB,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,gBAAgB,EAAE,OAAO,CAAC;KAC3B,CAAC;IACF,WAAW,EAAE;QACX,SAAS,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;IACF,SAAS,EAAE;QACT,aAAa,EAAE,OAAO,CAAC;QACvB,WAAW,EAAE,OAAO,CAAC;QACrB,UAAU,EAAE,OAAO,CAAC;QACpB,GAAG,EAAE,OAAO,CAAC;QACb,YAAY,EAAE,OAAO,CAAC;KACvB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,OAAO,CAAC,EAAE,qBAAqB,CAAC;IAChC,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,SAAS,CAAC,EAAE;QACV,aAAa,CAAC,EAAE,qBAAqB,CAAC;QACtC,WAAW,CAAC,EAAE,qBAAqB,CAAC;QACpC,UAAU,CAAC,EAAE,qBAAqB,CAAC;QACnC,GAAG,CAAC,EAAE,qBAAqB,CAAC;QAC5B,YAAY,CAAC,EAAE,qBAAqB,CAAC;KACtC,CAAC;CACH,CAAC;AAoDF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,qBAAqB,CAoJtF;AAmCD,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA+C7G"}
@@ -96,31 +96,31 @@ export function analyzeAgentReady(input) {
96
96
  };
97
97
  const robotsStatus = input.robotsTxt?.status ?? null;
98
98
  if (!isSuccessfulStatus(robotsStatus) || !robotsBody.trim()) {
99
- issues.push(issue("agent-robots-missing", "warning", "robots.txt is missing or empty; agents cannot discover crawl policy or Content Signals."));
99
+ issues.push(issue("agent-robots-missing", "warning", "robots.txt is missing or empty; agents cannot discover crawl policy or Content Signals.", "Add a robots.txt at the site root (for example public/robots.txt) with at least `User-agent: *` and a Sitemap line."));
100
100
  }
101
101
  else {
102
102
  if (contentSignals.length === 0) {
103
- issues.push(issue("agent-missing-content-signals", "warning", "robots.txt has no Content-Signal directives (ai-train / search / ai-input)."));
103
+ issues.push(issue("agent-missing-content-signals", "warning", "robots.txt has no Content-Signal directives (ai-train / search / ai-input).", "Add a Content-Signal under `User-agent: *` in robots.txt, for example `Content-Signal: search=yes,ai-train=no`."));
104
104
  }
105
105
  if (aiBotUserAgents.length === 0) {
106
- issues.push(issue("agent-missing-ai-bot-rules", "warning", "robots.txt has no explicit AI bot User-agent rules (e.g. GPTBot, ClaudeBot, Google-Extended)."));
106
+ issues.push(issue("agent-missing-ai-bot-rules", "warning", "robots.txt has no explicit AI bot User-agent rules (e.g. GPTBot, ClaudeBot, Google-Extended).", "Add explicit `User-agent` blocks for AI crawlers (GPTBot, ClaudeBot, Google-Extended, …) with Allow/Disallow as intended."));
107
107
  }
108
108
  }
109
109
  if (!llmsOk) {
110
- issues.push(issue("agent-missing-llms-txt", "warning", "llms.txt is missing or not a plain-text overview at /llms.txt."));
110
+ issues.push(issue("agent-missing-llms-txt", "warning", "llms.txt is missing or not a plain-text overview at /llms.txt.", "Add /llms.txt as plain text with an H1 title (`# Site Name`) and links to the most important content."));
111
111
  }
112
112
  else if (!llmsHasTitle) {
113
- issues.push(issue("agent-llms-txt-incomplete", "review", "llms.txt exists but has no H1 title line (# Site Name)."));
113
+ issues.push(issue("agent-llms-txt-incomplete", "review", "llms.txt exists but has no H1 title line (# Site Name).", "Start llms.txt with an H1 line, for example `# Site Name`."));
114
114
  }
115
115
  if (!markdownOk) {
116
- issues.push(issue("agent-missing-markdown-negotiation", "warning", "Homepage does not negotiate Markdown (Accept: text/markdown → Content-Type: text/markdown)."));
116
+ issues.push(issue("agent-missing-markdown-negotiation", "warning", "Homepage does not negotiate Markdown (Accept: text/markdown → Content-Type: text/markdown).", "When the request has `Accept: text/markdown`, return the page body as Markdown with `Content-Type: text/markdown` (middleware or route)."));
117
117
  }
118
118
  if (agentLinkRelations.length === 0) {
119
- issues.push(issue("agent-missing-link-headers", "review", 'Homepage has no agent-useful Link headers (rel="api-catalog", "service-doc", "service-desc", or "describedby").'));
119
+ issues.push(issue("agent-missing-link-headers", "review", 'Homepage has no agent-useful Link headers (rel="api-catalog", "service-doc", "service-desc", or "describedby").', 'Add Link response headers on the homepage, for example `Link: </.well-known/api-catalog>; rel="api-catalog"`.'));
120
120
  }
121
121
  const wellKnownPresent = Object.values(wellKnown).some(Boolean);
122
122
  if (!wellKnownPresent) {
123
- issues.push(issue("agent-missing-protocol-discovery", "review", "No agent protocol discovery endpoints found (MCP Server Card, Agent Skills, API Catalog, ARD, or A2A Agent Card)."));
123
+ issues.push(issue("agent-missing-protocol-discovery", "review", "No agent protocol discovery endpoints found (MCP Server Card, Agent Skills, API Catalog, ARD, or A2A Agent Card).", "Add /.well-known/agent-skills/index.json. Use an empty array if no skills are available (`skills: []`)."));
124
124
  }
125
125
  const findings = {
126
126
  robotsTxt: {
@@ -10,6 +10,10 @@ export type AnalyzeCheckOptions = {
10
10
  writeReport?: boolean;
11
11
  redirectOnly?: boolean;
12
12
  tier1?: Tier1ReportData;
13
+ /** npm package version recorded in the report (defaults to installed package). */
14
+ flightcheckVersion?: string;
15
+ /** CLI flags recorded in the report. */
16
+ flags?: string[];
13
17
  };
14
18
  export declare function runAnalyzeCheck(options: AnalyzeCheckOptions): Promise<AnalyzeCheckResult>;
15
19
  //# sourceMappingURL=analyze.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/checks/analyze.ts"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAW5F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB,CAAC;AAuBF,wBAAsB,eAAe,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA4C/F"}
1
+ {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/checks/analyze.ts"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAW5F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,kFAAkF;IAClF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAuBF,wBAAsB,eAAe,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA8C/F"}
@@ -37,6 +37,8 @@ export async function runAnalyzeCheck(options) {
37
37
  const report = await buildStructuredReport(inputPath, records, options.tier1, {
38
38
  redirectOnly,
39
39
  stackProfile,
40
+ flightcheckVersion: options.flightcheckVersion,
41
+ flags: options.flags,
40
42
  });
41
43
  // errorsOnly currently filters only the Markdown presentation of page-level noise;
42
44
  // the structured JSON keeps the full finding set for machine consumers.
@@ -1 +1 @@
1
- {"version":3,"file":"crawl.d.ts","sourceRoot":"","sources":["../../src/checks/crawl.ts"],"names":[],"mappings":"AAOA,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAKjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAC9E,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;CAC/B,CAAC,CAyDD;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiCzF"}
1
+ {"version":3,"file":"crawl.d.ts","sourceRoot":"","sources":["../../src/checks/crawl.ts"],"names":[],"mappings":"AAOA,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAMjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAC9E,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;CAC/B,CAAC,CAuED;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiCzF"}
@@ -2,6 +2,7 @@ import { DEFAULT_BURST_DELAY_MS, DEFAULT_CONCURRENCY, DEFAULT_DELAY_MS, DEFAULT_
2
2
  import { resolveActiveGroups } from "../crawler/checkGroups.js";
3
3
  import { buildDefaultOutputPaths } from "../crawler/outputPaths.js";
4
4
  import { runCrawler } from "../crawler/runCrawler.js";
5
+ import { selectSmartCrawlUrls } from "../crawler/smartPageSelection.js";
5
6
  import { collectSitemapPageUrls } from "./sitemap.js";
6
7
  import { isHttpUrl, matchesExcludePattern, normalizeUrl } from "../crawler/urlUtils.js";
7
8
  export async function prepareCrawlSeedUrls(options) {
@@ -51,6 +52,16 @@ export async function prepareCrawlSeedUrls(options) {
51
52
  }
52
53
  }
53
54
  seedUrls = seedUrls.filter((url) => !matchesExcludePattern(url, excludePatterns));
55
+ const unlimitedSeedCount = seedUrls.length;
56
+ const smart = selectSmartCrawlUrls(seedUrls, Math.floor(maxPages), {
57
+ startUrl: normalizedStartUrl,
58
+ });
59
+ seedUrls = smart.urls;
60
+ if (unlimitedSeedCount > seedUrls.length) {
61
+ // Intentionally concise: full URL lists stay in sitemap compare / crawl JSON.
62
+ const listCount = smart.tree.listParents.size;
63
+ console.info(`[crawl] smart max-pages: selected ${seedUrls.length}/${unlimitedSeedCount} seed URLs (limit=${Math.floor(maxPages)}, listParents=${listCount})`);
64
+ }
54
65
  return { normalizedStartUrl, seedUrls, outputPath, activeGroups };
55
66
  }
56
67
  export async function runCrawlCheck(options) {
@@ -1 +1 @@
1
- {"version":3,"file":"dns.d.ts","sourceRoot":"","sources":["../../src/checks/dns.ts"],"names":[],"mappings":"AAEA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,MAAM,UAAU,GAAG;IACvB,CAAC,EAAE,MAAM,EAAE,CAAC;IACZ,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,EAAE,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,EAAE,EAAE,MAAM,EAAE,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,UAAU,CAAC;IACpB,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB,CAAC;AA2CF,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAuC9E;AAED,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAE3E"}
1
+ {"version":3,"file":"dns.d.ts","sourceRoot":"","sources":["../../src/checks/dns.ts"],"names":[],"mappings":"AAEA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,MAAM,UAAU,GAAG;IACvB,CAAC,EAAE,MAAM,EAAE,CAAC;IACZ,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,EAAE,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,EAAE,EAAE,MAAM,EAAE,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,UAAU,CAAC;IACpB,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB,CAAC;AA2CF,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAqD9E;AAED,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAE3E"}
@@ -56,10 +56,10 @@ export async function analyzeDnsRecords(input) {
56
56
  const dmarc = (await resolveDmarc(hostname)) ?? findDmarc(hostname, flatTxt);
57
57
  const skipMailAuthIssues = isPlatformPreviewHostname(hostname);
58
58
  if (!skipMailAuthIssues && !spf) {
59
- issues.push(issue("missing-spf", "warning", "No SPF TXT record found."));
59
+ issues.push(issue("missing-spf", "warning", "No SPF TXT record found.", `Add a TXT record on ${hostname} starting with \`v=spf1\` that lists your mail senders.`));
60
60
  }
61
61
  if (!skipMailAuthIssues && !dmarc) {
62
- issues.push(issue("missing-dmarc", "warning", "No DMARC TXT record found."));
62
+ issues.push(issue("missing-dmarc", "warning", "No DMARC TXT record found.", `Add a TXT record on \`_dmarc.${hostname}\` starting with \`v=DMARC1;\` (for example \`p=none; rua=mailto:dmarc@${hostname}\`).`));
63
63
  }
64
64
  return {
65
65
  hostname,
@@ -3,6 +3,8 @@ export type CheckIssue = {
3
3
  id: string;
4
4
  severity: CheckIssueSeverity;
5
5
  message: string;
6
+ /** Concrete remediation suggestion for reports / agents. */
7
+ fix?: string;
6
8
  };
7
- export declare function issue(id: string, severity: CheckIssueSeverity, message: string): CheckIssue;
9
+ export declare function issue(id: string, severity: CheckIssueSeverity, message: string, fix?: string): CheckIssue;
8
10
  //# sourceMappingURL=issues.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"issues.d.ts","sourceRoot":"","sources":["../../src/checks/issues.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE7E,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAE3F"}
1
+ {"version":3,"file":"issues.d.ts","sourceRoot":"","sources":["../../src/checks/issues.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE7E,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,wBAAgB,KAAK,CACnB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,MAAM,EACf,GAAG,CAAC,EAAE,MAAM,GACX,UAAU,CAEZ"}
@@ -1,3 +1,3 @@
1
- export function issue(id, severity, message) {
2
- return { id, severity, message };
1
+ export function issue(id, severity, message, fix) {
2
+ return fix === undefined ? { id, severity, message } : { id, severity, message, fix };
3
3
  }
@@ -1 +1 @@
1
- {"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../src/checks/redirect.ts"],"names":[],"mappings":"AAEA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG1C,eAAO,MAAM,qBAAqB,aAAqC,CAAC;AACxE,eAAO,MAAM,+BAA+B,aAAsB,CAAC;AAEnE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,GAAG;IACrD,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;CAClC,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIzD;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,GAAG,MAAM,CAE/G;AAMD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAM7D;AAED,eAAO,MAAM,uBAAuB,EAAE,OAAO,EAAuB,CAAC;AAmBrE,wBAAgB,eAAe,CAAC,KAAK,EAAE,kBAAkB,GAAG,mBAAmB,CAyD9E;AAED,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,KAAK,GAAG,QAAQ,GAAG,UAAU,GAAG,gBAAgB,GAAG,cAAc,CAAC,GACjG,UAAU,EAAE,CAyEd;AAED,wBAAgB,iBAAiB,CAAC,eAAe,EAAE,OAAO,EAAE,GAAG,OAAO,CAKrE;AAED,wBAAgB,wBAAwB,CACtC,eAAe,EAAE,OAAO,EAAE,EAC1B,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAAE,cAAc,CAAC,GAClD,OAAO,EAAE,CAWX;AAED,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,OAAO,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAGzG;AA0DD,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAqB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA8BrH"}
1
+ {"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../src/checks/redirect.ts"],"names":[],"mappings":"AAEA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG1C,eAAO,MAAM,qBAAqB,aAAqC,CAAC;AACxE,eAAO,MAAM,+BAA+B,aAAsB,CAAC;AAEnE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,GAAG;IACrD,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;CAClC,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIzD;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,GAAG,MAAM,CAE/G;AAMD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAM7D;AAED,eAAO,MAAM,uBAAuB,EAAE,OAAO,EAAuB,CAAC;AAmBrE,wBAAgB,eAAe,CAAC,KAAK,EAAE,kBAAkB,GAAG,mBAAmB,CA0E9E;AAED,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,KAAK,GAAG,QAAQ,GAAG,UAAU,GAAG,gBAAgB,GAAG,cAAc,CAAC,GACjG,UAAU,EAAE,CAgFd;AAED,wBAAgB,iBAAiB,CAAC,eAAe,EAAE,OAAO,EAAE,GAAG,OAAO,CAKrE;AAED,wBAAgB,wBAAwB,CACtC,eAAe,EAAE,OAAO,EAAE,EAC1B,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAAE,cAAc,CAAC,GAClD,OAAO,EAAE,CAWX;AAED,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,OAAO,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAGzG;AA0DD,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAqB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA8BrH"}
@@ -62,19 +62,19 @@ export function analyzeRedirect(input) {
62
62
  };
63
63
  }
64
64
  if (!input.location) {
65
- issues.push(issue("redirect-missing-location", "error", "Redirect response is missing a Location header."));
65
+ issues.push(issue("redirect-missing-location", "error", "Redirect response is missing a Location header.", "Include a valid Location header on redirect responses pointing at the canonical URL."));
66
66
  }
67
67
  else if (!input.resolvedTarget) {
68
- issues.push(issue("redirect-invalid-location", "error", "Redirect Location header is not a valid URL."));
68
+ issues.push(issue("redirect-invalid-location", "error", "Redirect Location header is not a valid URL.", "Set Location to an absolute http(s) URL for the redirect target."));
69
69
  }
70
70
  if (input.status !== null && !PERMANENT_REDIRECT_STATUS_CODES.has(input.status)) {
71
- issues.push(issue("redirect-temporary-status", "warning", `Redirect uses status ${input.status}; use 301 or 308 for permanent domain redirects.`));
71
+ issues.push(issue("redirect-temporary-status", "warning", `Redirect uses status ${input.status}; use 301 or 308 for permanent domain redirects.`, "Change the redirect status to 301 or 308 for permanent domain redirects."));
72
72
  }
73
73
  if (isHttps && !input.headers["strict-transport-security"]) {
74
- issues.push(issue("redirect-missing-hsts", "warning", "Strict-Transport-Security header is missing on the redirect response."));
74
+ issues.push(issue("redirect-missing-hsts", "warning", "Strict-Transport-Security header is missing on the redirect response.", "Send Strict-Transport-Security on the HTTPS redirect response as well as on final pages."));
75
75
  }
76
76
  if (input.resolvedTarget && input.targetStatus !== null && input.targetStatus >= 400) {
77
- issues.push(issue("redirect-target-unreachable", "error", `Redirect target returned status ${input.targetStatus}: ${input.resolvedTarget}`));
77
+ issues.push(issue("redirect-target-unreachable", "error", `Redirect target returned status ${input.targetStatus}: ${input.resolvedTarget}`, "Fix the redirect target so it returns a successful response, or point Location at a working URL."));
78
78
  }
79
79
  return {
80
80
  ...input,
@@ -87,27 +87,27 @@ export function analyzeWwwAliasRedirect(canonicalUrl, probe) {
87
87
  const issues = [];
88
88
  const canonicalHost = hostnameFromUrl(canonicalUrl);
89
89
  if (probe.status === null) {
90
- issues.push(issue("redirect-www-alias-unreachable", "error", `Alternate host is unreachable and should redirect to ${canonicalHost}: ${probe.url}`));
90
+ issues.push(issue("redirect-www-alias-unreachable", "error", `Alternate host is unreachable and should redirect to ${canonicalHost}: ${probe.url}`, `Configure DNS and hosting so the alternate host responds and permanently redirects to ${canonicalHost}.`));
91
91
  return issues;
92
92
  }
93
93
  if (!REDIRECT_STATUS_CODES.has(probe.status)) {
94
- issues.push(issue("redirect-www-alias-no-redirect", "error", `Alternate host should redirect to ${canonicalHost} but returned status ${probe.status}: ${probe.url}`));
94
+ issues.push(issue("redirect-www-alias-no-redirect", "error", `Alternate host should redirect to ${canonicalHost} but returned status ${probe.status}: ${probe.url}`, `Configure the alternate host to respond with 301/308 Location pointing at ${canonicalHost}.`));
95
95
  return issues;
96
96
  }
97
97
  if (!probe.location) {
98
- issues.push(issue("redirect-www-alias-missing-location", "error", `Alternate host redirect is missing a Location header: ${probe.url}`));
98
+ issues.push(issue("redirect-www-alias-missing-location", "error", `Alternate host redirect is missing a Location header: ${probe.url}`, `Add a Location header on the alternate-host redirect targeting ${canonicalHost}.`));
99
99
  }
100
100
  else if (!probe.resolvedTarget) {
101
- issues.push(issue("redirect-www-alias-invalid-location", "error", `Alternate host redirect Location header is not a valid URL: ${probe.url}`));
101
+ issues.push(issue("redirect-www-alias-invalid-location", "error", `Alternate host redirect Location header is not a valid URL: ${probe.url}`, `Set Location to an absolute http(s) URL on ${canonicalHost}.`));
102
102
  }
103
103
  else if (hostnameFromUrl(probe.resolvedTarget) !== canonicalHost) {
104
- issues.push(issue("redirect-www-alias-wrong-target", "error", `Alternate host redirects to ${probe.resolvedTarget} instead of ${canonicalHost}: ${probe.url}`));
104
+ issues.push(issue("redirect-www-alias-wrong-target", "error", `Alternate host redirects to ${probe.resolvedTarget} instead of ${canonicalHost}: ${probe.url}`, `Point the alternate-host redirect at the canonical hostname (${canonicalHost}).`));
105
105
  }
106
106
  if (probe.status !== null && !PERMANENT_REDIRECT_STATUS_CODES.has(probe.status)) {
107
- issues.push(issue("redirect-www-alias-temporary-status", "warning", `Alternate host redirect uses status ${probe.status}; use 301 or 308 for permanent domain redirects: ${probe.url}`));
107
+ issues.push(issue("redirect-www-alias-temporary-status", "warning", `Alternate host redirect uses status ${probe.status}; use 301 or 308 for permanent domain redirects: ${probe.url}`, "Change the alternate-host redirect status to 301 or 308."));
108
108
  }
109
109
  if (probe.resolvedTarget && probe.targetStatus !== null && probe.targetStatus >= 400) {
110
- issues.push(issue("redirect-www-alias-target-unreachable", "error", `Alternate host redirect target returned status ${probe.targetStatus}: ${probe.resolvedTarget}`));
110
+ issues.push(issue("redirect-www-alias-target-unreachable", "error", `Alternate host redirect target returned status ${probe.targetStatus}: ${probe.resolvedTarget}`, "Fix the canonical target URL so the alternate-host redirect lands on a successful page."));
111
111
  }
112
112
  return issues;
113
113
  }
@@ -1 +1 @@
1
- {"version":3,"file":"robots-txt.d.ts","sourceRoot":"","sources":["../../src/checks/robots-txt.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAGrD,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB,CAAC;AAgDF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,oBAAoB,CA2CnF;AAED,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAmB3G"}
1
+ {"version":3,"file":"robots-txt.d.ts","sourceRoot":"","sources":["../../src/checks/robots-txt.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAGrD,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB,CAAC;AAgDF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,oBAAoB,CAqFnF;AAED,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAmB3G"}
@@ -47,15 +47,15 @@ export function analyzeRobotsTxt(input) {
47
47
  const issues = [];
48
48
  const { url, status, body } = input;
49
49
  if (status === null || status === 404) {
50
- issues.push(issue("robots-missing", "warning", "robots.txt is missing."));
50
+ issues.push(issue("robots-missing", "warning", "robots.txt is missing.", "Publish /robots.txt at the site origin with at least `User-agent: *` and a Sitemap directive."));
51
51
  return { url, status, body, sitemapUrls: [], issues };
52
52
  }
53
53
  if (!body) {
54
- issues.push(issue("robots-unparseable", "error", "robots.txt response body is empty."));
54
+ issues.push(issue("robots-unparseable", "error", "robots.txt response body is empty.", "Return a non-empty robots.txt body with valid User-agent / Allow / Disallow / Sitemap lines."));
55
55
  return { url, status, body, sitemapUrls: [], issues };
56
56
  }
57
57
  if (hasMalformedRobotsLines(body)) {
58
- issues.push(issue("robots-unparseable", "error", "robots.txt contains malformed directives."));
58
+ issues.push(issue("robots-unparseable", "error", "robots.txt contains malformed directives.", "Fix robots.txt syntax: one directive per line (`User-agent:`, `Allow:`, `Disallow:`, `Sitemap:`)."));
59
59
  }
60
60
  let sitemapUrls = [];
61
61
  try {
@@ -64,17 +64,17 @@ export function analyzeRobotsTxt(input) {
64
64
  sitemapUrls = parsed.getSitemaps();
65
65
  }
66
66
  catch {
67
- issues.push(issue("robots-unparseable", "error", "robots.txt could not be parsed."));
67
+ issues.push(issue("robots-unparseable", "error", "robots.txt could not be parsed.", "Validate robots.txt content and encoding; ensure it is plain text with standard directives."));
68
68
  return { url, status, body, sitemapUrls: [], issues };
69
69
  }
70
70
  if (sitemapUrls.length === 0) {
71
71
  sitemapUrls = extractSitemapUrls(body);
72
72
  }
73
73
  if (blocksAllUserAgents(body, url)) {
74
- issues.push(issue("robots-blocks-all", "critical", "robots.txt disallows all crawling for User-agent: *."));
74
+ issues.push(issue("robots-blocks-all", "critical", "robots.txt disallows all crawling for User-agent: *.", "Remove or narrow `Disallow: /` under `User-agent: *` so search engines and agents can crawl public pages."));
75
75
  }
76
76
  if (sitemapUrls.length === 0) {
77
- issues.push(issue("robots-missing-sitemap", "warning", "robots.txt has no Sitemap directive."));
77
+ issues.push(issue("robots-missing-sitemap", "warning", "robots.txt has no Sitemap directive.", "Add a Sitemap line pointing at your sitemap, for example `Sitemap: https://example.com/sitemap.xml`."));
78
78
  }
79
79
  return { url, status, body, sitemapUrls, issues };
80
80
  }
@@ -1 +1 @@
1
- {"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../../src/checks/security-headers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAWF,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,6BAAkC,GAC1C,UAAU,EAAE,CA0Cd;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB,CAAC;AAEF,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAgBnG"}
1
+ {"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../../src/checks/security-headers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAWF,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,6BAAkC,GAC1C,UAAU,EAAE,CA8Ed;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB,CAAC;AAEF,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAgBnG"}
@@ -17,22 +17,22 @@ export function analyzeSecurityHeaders(headers, options = {}) {
17
17
  const referrerPolicy = headerValue(headers, "referrer-policy");
18
18
  const permissionsPolicy = headerValue(headers, "permissions-policy");
19
19
  if (isHttps && !hsts) {
20
- issues.push(issue("missing-hsts", "error", "Strict-Transport-Security header is missing on HTTPS."));
20
+ issues.push(issue("missing-hsts", "error", "Strict-Transport-Security header is missing on HTTPS.", "Send `Strict-Transport-Security: max-age=31536000; includeSubDomains` on HTTPS responses (hosting or app headers)."));
21
21
  }
22
22
  if (!csp) {
23
- issues.push(issue("missing-csp", "warning", "Content-Security-Policy header is missing."));
23
+ issues.push(issue("missing-csp", "warning", "Content-Security-Policy header is missing.", "Add a Content-Security-Policy header tailored to your app (start with report-only if needed)."));
24
24
  }
25
25
  if (!xFrameOptions && !hasFrameAncestorsCsp(csp)) {
26
- issues.push(issue("missing-clickjacking-protection", "warning", "Neither X-Frame-Options nor CSP frame-ancestors is set."));
26
+ issues.push(issue("missing-clickjacking-protection", "warning", "Neither X-Frame-Options nor CSP frame-ancestors is set.", "Set `X-Frame-Options: DENY` (or SAMEORIGIN) or add `frame-ancestors` to your Content-Security-Policy."));
27
27
  }
28
28
  if (!nosniff || nosniff.toLowerCase() !== "nosniff") {
29
- issues.push(issue("missing-nosniff", "warning", "X-Content-Type-Options: nosniff is missing."));
29
+ issues.push(issue("missing-nosniff", "warning", "X-Content-Type-Options: nosniff is missing.", "Send `X-Content-Type-Options: nosniff` on all responses."));
30
30
  }
31
31
  if (!referrerPolicy) {
32
- issues.push(issue("missing-referrer-policy", "review", "Referrer-Policy header is missing."));
32
+ issues.push(issue("missing-referrer-policy", "review", "Referrer-Policy header is missing.", "Send a Referrer-Policy such as `strict-origin-when-cross-origin`."));
33
33
  }
34
34
  if (!permissionsPolicy) {
35
- issues.push(issue("missing-permissions-policy", "review", "Permissions-Policy header is missing."));
35
+ issues.push(issue("missing-permissions-policy", "review", "Permissions-Policy header is missing.", "Add a Permissions-Policy header that disables unused browser features (camera, mic, geolocation, …)."));
36
36
  }
37
37
  return issues;
38
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sitemap.d.ts","sourceRoot":"","sources":["../../src/checks/sitemap.ts"],"names":[],"mappings":"AAGA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAMrD,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B,CAAC;AAQF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,GAAG,kBAAkB,CA6G3F;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CAIpG;AAkBD,wBAAsB,kBAAkB,CACtC,aAAa,EAAE,MAAM,EAAE,EACvB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAchC;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,IAAI,CACpC,sBAAsB,EACtB,WAAW,GAAG,0BAA0B,GAAG,iBAAiB,CAC7D,CAAC;AAEF,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAYlG;AAED,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,iBAAiB,GAAE,MAAM,EAAO,EAChC,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBnB"}
1
+ {"version":3,"file":"sitemap.d.ts","sourceRoot":"","sources":["../../src/checks/sitemap.ts"],"names":[],"mappings":"AAGA,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAMrD,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B,CAAC;AAQF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,GAAG,kBAAkB,CAyH3F;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CAIpG;AAkBD,wBAAsB,kBAAkB,CACtC,aAAa,EAAE,MAAM,EAAE,EACvB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAchC;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,IAAI,CACpC,sBAAsB,EACtB,WAAW,GAAG,0BAA0B,GAAG,iBAAiB,CAC7D,CAAC;AAEF,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAYlG;AAED,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,iBAAiB,GAAE,MAAM,EAAO,EAChC,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBnB"}