pagegraph 0.12.0 → 0.13.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 (88) hide show
  1. package/README.md +277 -25
  2. package/dist/audit.d.ts +2 -2
  3. package/dist/audit.js +4 -4
  4. package/dist/build-only/audit.js +1 -1
  5. package/dist/build-only/claims.d.ts +1 -0
  6. package/dist/build-only/claims.js +6 -0
  7. package/dist/build-only/claims.js.map +1 -0
  8. package/dist/build-only/config.js +1 -1
  9. package/dist/build-only/oxlint.d.ts +1 -0
  10. package/dist/build-only/oxlint.js +6 -0
  11. package/dist/build-only/oxlint.js.map +1 -0
  12. package/dist/build-only/tanstack-start.js +1 -1
  13. package/dist/build-only/vite.js +1 -1
  14. package/dist/{checks-uZk5Em9A.js → checks-BS-aDXB_.js} +2 -2
  15. package/dist/checks-BS-aDXB_.js.map +1 -0
  16. package/dist/claims-Bch7Lxlo.d.ts +92 -0
  17. package/dist/claims-Bv7voeEV.js +431 -0
  18. package/dist/claims-Bv7voeEV.js.map +1 -0
  19. package/dist/claims.d.ts +94 -0
  20. package/dist/claims.js +2 -0
  21. package/dist/cli.js +10095 -8318
  22. package/dist/cli.js.map +1 -1
  23. package/dist/config.d.ts +4 -24
  24. package/dist/config.js +2 -2
  25. package/dist/config.js.map +1 -1
  26. package/dist/{content-kcEnQdfi.js → content-CHd0X4s6.js} +2 -2
  27. package/dist/{content-kcEnQdfi.js.map → content-CHd0X4s6.js.map} +1 -1
  28. package/dist/{content-B1cPtL8b.d.ts → content-DgNr7apS.d.ts} +4 -5
  29. package/dist/declare-D5o7hwmv.js.map +1 -1
  30. package/dist/document-CztEu3Vd.d.ts +109 -0
  31. package/dist/document-Dvemcv7V.js +379 -0
  32. package/dist/document-Dvemcv7V.js.map +1 -0
  33. package/dist/facts-1OKclPOk.js +115 -0
  34. package/dist/facts-1OKclPOk.js.map +1 -0
  35. package/dist/{declare-DVgdEdOL.d.ts → graph-D1PIndsC.d.ts} +117 -6
  36. package/dist/{graph-Bcsourlv.js → graph-DMNOLXLu.js} +23 -13
  37. package/dist/graph-DMNOLXLu.js.map +1 -0
  38. package/dist/index-C5w9ripz.d.ts +204 -0
  39. package/dist/index.d.ts +6 -160
  40. package/dist/index.js +37 -5
  41. package/dist/index.js.map +1 -1
  42. package/dist/inline-BbS93GAT.js +682 -0
  43. package/dist/inline-BbS93GAT.js.map +1 -0
  44. package/dist/llms-Dhr_5u2O.js +19 -0
  45. package/dist/llms-Dhr_5u2O.js.map +1 -0
  46. package/dist/markdown-C6dRE9zD.js +168 -0
  47. package/dist/markdown-C6dRE9zD.js.map +1 -0
  48. package/dist/markdown-path-lJB9BAVZ.js +6 -0
  49. package/dist/markdown-path-lJB9BAVZ.js.map +1 -0
  50. package/dist/{message-DT_CjUdt.js → message-CuBjgZ74.js} +2 -2
  51. package/dist/{message-DT_CjUdt.js.map → message-CuBjgZ74.js.map} +1 -1
  52. package/dist/oxlint.d.ts +58 -0
  53. package/dist/oxlint.js +202 -0
  54. package/dist/oxlint.js.map +1 -0
  55. package/dist/page-heads-BfjWQvau.d.ts +108 -0
  56. package/dist/{page-heads-6y-CARRQ.js → page-heads-BgbaIjj9.js} +3 -3
  57. package/dist/page-heads-BgbaIjj9.js.map +1 -0
  58. package/dist/{projections-DeEFe7XT.d.ts → projections-Bk-qOOsR.d.ts} +2 -2
  59. package/dist/react.d.ts +3 -2
  60. package/dist/react.js +2 -1
  61. package/dist/react.js.map +1 -1
  62. package/dist/tanstack-start/index.d.ts +29 -13
  63. package/dist/tanstack-start/index.js +391 -42
  64. package/dist/tanstack-start/index.js.map +1 -1
  65. package/dist/tanstack-start/markdown.d.ts +12 -0
  66. package/dist/tanstack-start/markdown.js +149 -0
  67. package/dist/tanstack-start/markdown.js.map +1 -0
  68. package/dist/tanstack-start/prerender-worker.d.ts +2 -0
  69. package/dist/tanstack-start/prerender-worker.js +7 -0
  70. package/dist/tanstack-start/prerender-worker.js.map +1 -0
  71. package/dist/tanstack-start/react.d.ts +7 -0
  72. package/dist/tanstack-start/react.js +12 -0
  73. package/dist/tanstack-start/react.js.map +1 -0
  74. package/dist/tanstack-start/server.d.ts +9 -3
  75. package/dist/tanstack-start/server.js +26 -2
  76. package/dist/tanstack-start/server.js.map +1 -1
  77. package/dist/{vite-graph-loader-01p1WxEq.js → vite-graph-loader-CsGiYNWE.js} +6 -5
  78. package/dist/vite-graph-loader-CsGiYNWE.js.map +1 -0
  79. package/dist/vite-graph-loader-DZCwejuJ.d.ts +133 -0
  80. package/package.json +44 -3
  81. package/dist/checks-uZk5Em9A.js.map +0 -1
  82. package/dist/graph-Bcsourlv.js.map +0 -1
  83. package/dist/graph-BxCY90vq.d.ts +0 -104
  84. package/dist/graph-CBIY71w6.d.ts +0 -57
  85. package/dist/page-heads-6y-CARRQ.js.map +0 -1
  86. package/dist/page-heads-DNkjMUEN.d.ts +0 -21
  87. package/dist/vite-graph-loader-01p1WxEq.js.map +0 -1
  88. package/dist/vite-graph-loader-Btgjz5RH.d.ts +0 -56
package/dist/index.d.ts CHANGED
@@ -1,160 +1,6 @@
1
- import { a as SeoFaq, c as SitemapPolicy, i as RouteSeo, l as applyTitleTemplate, n as PublicPath, o as SeoKind, r as Register, s as SeoPageHead, t as JsonLdDocument } from "./declare-DVgdEdOL.js";
2
- import { a as SeoGraph, c as SeoNodeHead, d as buildSeoGraph, i as SeoEdgeType, l as SeoRouteNode, n as SeoCollection, o as SeoInstance, r as SeoEdge, s as SeoNode, t as BuildSeoGraphInput, u as SeoSource } from "./graph-BxCY90vq.js";
3
- import { A as RenderedEdgeFailure, B as FreshnessEntry, C as Anchor, D as RENDERED_EDGE_ARTIFACT_SCHEMA_VERSION, E as LinkGraphDiff, F as decodeRenderedEdgeArtifact, H as FreshnessReport, I as diffLinkGraph, L as extractAnchors, M as RenderedPage, N as SimpleEdge, O as RenderedEdgeArtifact, P as buildRenderedGraph, R as normalizePath, S as hasStructuralViolations, T as LinkEdge, U as freshnessReport, V as FreshnessPolicy, _ as Violation, a as Heading, b as checkGraph, c as RenderedDocument, d as checkRenderedContent, f as extractPageContent, g as Severity, h as CoverageRule, i as ContentRuleName, j as RenderedGraph, k as RenderedEdgeArtifactCrawl, l as WordCountRule, m as CheckGraphOptions, n as ContentFinding, o as JsonLdNode, p as renderedCheckTargets, r as ContentPolicy, s as PageContent, t as ContentCheckOptions, u as checkPageContent, v as checkCoverage, w as AnchorRegion, x as checkRenderedCoverage, y as checkFreshness, z as renderedGraphFromEdges } from "./content-B1cPtL8b.js";
4
- import { a as inspectNode, c as renderRobots, i as contentSignal, l as renderSitemap, n as ProjectionConfig, o as isSitemapEligible, r as RobotsConfig, s as lastModified, t as NodeReport } from "./projections-DeEFe7XT.js";
5
- import { i as selectPageHeadNodes, n as PageHeadsOptions, r as pageHeads, t as PageHead } from "./page-heads-DNkjMUEN.js";
6
- import { AnyRouter } from "@tanstack/react-router";
7
- //#region src/core/collections.d.ts
8
- /** What a page contributes: its instance metadata plus the paths it links to deliberately. */
9
- interface ContentEntry extends Omit<SeoInstance, "path"> {
10
- /** Defaults to the page's `url`. */
11
- readonly path?: string | undefined;
12
- /** Deliberate cross-links, emitted as `related` edges from this page. */
13
- readonly related?: ReadonlyArray<string> | undefined;
14
- }
15
- interface ContentCollectionOptions<Page> {
16
- /** The param route these pages render through, e.g. `/blog/$slug`. */
17
- readonly route: PublicPath;
18
- /** The `source` label stamped on every node, e.g. `"blog"`. */
19
- readonly source: SeoSource;
20
- readonly pages: ReadonlyArray<Page>;
21
- /** Map one page to its entry; decode frontmatter here. */
22
- readonly entry: (page: Page) => ContentEntry;
23
- /** Edges beyond `related` (crumb parents, collection membership). */
24
- readonly edges?: ReadonlyArray<SeoEdge> | undefined;
25
- }
26
- declare function contentCollection<Page extends {
27
- readonly url: string;
28
- }>(options: ContentCollectionOptions<Page>): SeoCollection;
29
- //#endregion
30
- //#region src/core/wire.d.ts
31
- interface SeoGraphJson {
32
- readonly nodes: ReadonlyArray<SeoNode>;
33
- readonly edges: ReadonlyArray<SeoEdge>;
34
- readonly collisions?: ReadonlyArray<{
35
- readonly path: string;
36
- readonly sources: ReadonlyArray<SeoSource>;
37
- }> | undefined;
38
- }
39
- declare function graphToJson(graph: SeoGraph): SeoGraphJson;
40
- declare function graphFromJson(json: SeoGraphJson): SeoGraph;
41
- //#endregion
42
- //#region src/core/link-candidates.d.ts
43
- /** One proposed contextual link, in the direction the link would be authored. */
44
- interface LinkCandidatePair {
45
- readonly source: string;
46
- readonly destination: string;
47
- /** The cluster the pair shares: a top-level section, or `kind:<kind>`. */
48
- readonly cluster: string;
49
- /** Why the pair is plausible, e.g. `same top-level section "/blog"`. */
50
- readonly reason: string;
51
- }
52
- interface LinkCandidateOptions {
53
- /** Maximum candidates to return after ordering (default 50). */
54
- readonly limit?: number;
55
- /** Restrict to these clusters; a filter may be a section or a bare kind. */
56
- readonly clusters?: ReadonlyArray<string>;
57
- /** Anchors already served in HTML. Only body-region edges exclude their own direction. */
58
- readonly renderedEdges?: ReadonlyArray<SimpleEdge & {
59
- readonly region?: AnchorRegion;
60
- }>;
61
- }
62
- /** One cluster and how many candidate pairs it contributes (before `limit`). */
63
- interface LinkClusterSummary {
64
- readonly key: string;
65
- readonly candidates: number;
66
- }
67
- interface LinkCandidateResult {
68
- readonly candidates: ReadonlyArray<LinkCandidatePair>;
69
- /** Candidate pairs before `limit` was applied (after cluster filters). */
70
- readonly total: number;
71
- readonly truncated: boolean;
72
- readonly clusters: ReadonlyArray<LinkClusterSummary>;
73
- }
74
- /** Directionless pair key, so an edge in either direction means "connected". */
75
- declare const undirectedEdgeKey: (from: string, to: string) => string;
76
- /** A `--cluster` filter matches a section by name, or a kind via its bare label. */
77
- declare const matchesClusterFilter: (cluster: string, filter: string) => boolean;
78
- /** Human reason string for a candidate's source page, used when handing a plan to Jev. */
79
- declare const candidateSourceText: (node: SeoNode) => string;
80
- /**
81
- * Enumerate reviewable contextual-link candidates from the declared graph.
82
- *
83
- * Excluded: self-pairs, pages that are not sitemap-eligible, pairs already
84
- * declared as a `related` edge, and — when {@link LinkCandidateOptions.renderedEdges}
85
- * is supplied — pairs already rendered as an anchor. Queries, hashes, and
86
- * trailing slashes are normalized so both sides compare by the graph's path key.
87
- */
88
- declare const generateLinkCandidates: (graph: SeoGraph, options?: LinkCandidateOptions) => LinkCandidateResult;
89
- /**
90
- * Decode a rendered-edge dump supplied to the CLI: either a bare array of
91
- * `{ from, to }` edges, or an object carrying `edges` (or `internalEdges`).
92
- * Throws on any other shape — this is user input, not a provider payload.
93
- */
94
- declare const decodeRenderedEdges: (input: unknown) => ReadonlyArray<SimpleEdge & {
95
- readonly region?: AnchorRegion;
96
- }>;
97
- //#endregion
98
- //#region src/core/inspect-html.d.ts
99
- /**
100
- * Read a rendered `<head>` and validate it: title, meta
101
- * (description/robots/og/twitter), canonical link, and `application/ld+json`
102
- * blocks, with a minimal per-type JSON-LD check.
103
- *
104
- * This is the pure half of `pagegraph inspect --live` — the half worth having on its
105
- * own. The CLI fetches a URL and hands the body here; a test suite can render a
106
- * page and hand *that* here, asserting the head it actually ships. Both get the
107
- * same verdict, because it is the same function.
108
- *
109
- * No HTML-parsing dependency, by design: a `<head>` is small and well-formed, so
110
- * string/regex scanning is enough, and the package's core stays zero-dependency
111
- * (which is also why the object guard below is hand-rolled — `effect/Predicate`
112
- * is not reachable from this entry). It is honest about its limits: it does not
113
- * build a DOM, so exotic markup (commented-out tags, CDATA, attributes spanning
114
- * constructs) is out of scope. This validates *rendered output*, not arbitrary
115
- * HTML.
116
- *
117
- * `issues` are blocking: a non-empty list makes `pagegraph inspect --live` exit 1.
118
- * Required tags are `<title>`, `meta[name=description]`, and
119
- * `link[rel=canonical]`; any JSON-LD that fails to parse or fails its minimal
120
- * schema is also blocking.
121
- */
122
- interface JsonLdReport {
123
- type: string;
124
- valid: boolean;
125
- errors: Array<string>;
126
- }
127
- interface LiveHeadReport {
128
- url: string;
129
- status: number;
130
- title?: string | undefined;
131
- description?: string | undefined;
132
- canonical?: string | undefined;
133
- robots?: string | undefined;
134
- og: Record<string, string>;
135
- twitter: Record<string, string>;
136
- jsonLd: Array<JsonLdReport>;
137
- issues: Array<string>;
138
- }
139
- /** Parse a rendered HTML document's `<head>` into a report. Pure. */
140
- declare const inspectHtml: (url: string, status: number, html: string) => LiveHeadReport;
141
- /** A non-empty `issues` list fails `pagegraph inspect --live` (exit 1). */
142
- declare const hasBlockingIssues: (report: LiveHeadReport) => boolean;
143
- //#endregion
144
- //#region src/core/resolve-route-link.d.ts
145
- /**
146
- * Resolve a route's declared `seo.link` card (title + description) by its full
147
- * path.
148
- *
149
- * `useRouter()` returns a Router typed to this app's exact route tree, so
150
- * `routesByPath` is keyed by the literal `FileRouteTypes["fullPaths"]` union —
151
- * but callers here (declared `related` targets) hold arbitrary runtime path
152
- * strings, not that literal type. `routesByPath` and `routesById` are plain
153
- * Records on every Router instance regardless of which route tree it's
154
- * parameterized over, so this narrows to that structural shape once, at this
155
- * boundary, instead of threading an `AnyRoute` cast through every call site.
156
- */
157
- declare function resolveRouteLink(router: AnyRouter, path: string): RouteSeo["link"];
158
- //#endregion
159
- export { type Anchor, type AnchorRegion, type BuildSeoGraphInput, type CheckGraphOptions, type ContentCheckOptions, type ContentCollectionOptions, type ContentEntry, type ContentFinding, type ContentPolicy, type ContentRuleName, type CoverageRule, type FreshnessEntry, type FreshnessPolicy, type FreshnessReport, type Heading, type JsonLdDocument, type JsonLdNode, type JsonLdReport, type LinkCandidateOptions, type LinkCandidatePair, type LinkCandidateResult, type LinkClusterSummary, type LinkEdge, type LinkGraphDiff, type LiveHeadReport, type NodeReport, type PageContent, type PageHead, type PageHeadsOptions, type ProjectionConfig, type PublicPath, RENDERED_EDGE_ARTIFACT_SCHEMA_VERSION, type Register, type RenderedDocument, type RenderedEdgeArtifact, type RenderedEdgeArtifactCrawl, type RenderedEdgeFailure, type RenderedGraph, type RenderedPage, type RobotsConfig, type RouteSeo, type SeoCollection, type SeoEdge, type SeoEdgeType, type SeoFaq, type SeoGraph, type SeoGraphJson, type SeoInstance, type SeoKind, type SeoNode, type SeoNodeHead, type SeoPageHead, type SeoRouteNode, type SeoSource, type Severity, type SimpleEdge, type SitemapPolicy, type Violation, type WordCountRule, applyTitleTemplate, buildRenderedGraph, buildSeoGraph, candidateSourceText, checkCoverage, checkFreshness, checkGraph, checkPageContent, checkRenderedContent, checkRenderedCoverage, contentCollection, contentSignal, decodeRenderedEdgeArtifact, decodeRenderedEdges, diffLinkGraph, extractAnchors, extractPageContent, freshnessReport, generateLinkCandidates, graphFromJson, graphToJson, hasBlockingIssues, hasStructuralViolations, inspectHtml, inspectNode, isSitemapEligible, lastModified, matchesClusterFilter, normalizePath, pageHeads, renderRobots, renderSitemap, renderedCheckTargets, renderedGraphFromEdges, resolveRouteLink, selectPageHeadNodes, undirectedEdgeKey };
160
- //# sourceMappingURL=index.d.ts.map
1
+ import { _ as SeoKind, a as SeoGraph, b as applyTitleTemplate, c as SeoNodeHead, d as buildSeoGraph, f as JsonLdDocument, g as SeoFaq, h as RouteSeo, i as SeoEdgeType, l as SeoRouteNode, m as Register, n as SeoCollection, o as SeoInstance, p as PublicPath, r as SeoEdge, s as SeoNode, t as BuildSeoGraphInput, u as SeoSource, v as SeoPageHead, y as SitemapPolicy } from "./graph-D1PIndsC.js";
2
+ import { A as RenderedEdgeFailure, B as FreshnessEntry, C as Anchor, D as RENDERED_EDGE_ARTIFACT_SCHEMA_VERSION, E as LinkGraphDiff, F as decodeRenderedEdgeArtifact, H as FreshnessReport, I as diffLinkGraph, L as extractAnchors, M as RenderedPage, N as SimpleEdge, O as RenderedEdgeArtifact, P as buildRenderedGraph, R as normalizePath, S as hasStructuralViolations, T as LinkEdge, U as freshnessReport, V as FreshnessPolicy, _ as Violation, a as Heading, b as checkGraph, c as RenderedDocument, d as checkRenderedContent, f as extractPageContent, g as Severity, h as CoverageRule, i as ContentRuleName, j as RenderedGraph, k as RenderedEdgeArtifactCrawl, l as WordCountRule, m as CheckGraphOptions, n as ContentFinding, o as JsonLdNode, p as renderedCheckTargets, r as ContentPolicy, s as PageContent, t as ContentCheckOptions, u as checkPageContent, v as checkCoverage, w as AnchorRegion, x as checkRenderedCoverage, y as checkFreshness, z as renderedGraphFromEdges } from "./content-DgNr7apS.js";
3
+ import { _ as hashDocument, a as FactDefinition, c as fact, d as InlineNode, f as Message, g as VisualDocument, h as SectionItem, i as selectPageHeadNodes, l as resolveFact, m as SectionDocument, n as PageHeadsOptions, o as Facts, p as PageDocument, r as pageHeads, s as defineFacts, t as PageHead, u as BodyEntry } from "./page-heads-BfjWQvau.js";
4
+ import { A as sectionMarkdown, C as LlmsPage, D as SectionKind, E as MarkdownWriter, O as defineSectionKind, S as createMarkdownLock, T as llmsSection, _ as graphToJson, a as inspectHtml, b as contentCollection, c as LinkCandidateResult, d as decodeRenderedEdges, f as generateLinkCandidates, g as graphFromJson, h as SeoGraphJson, i as hasBlockingIssues, j as contentHash, k as documentMarkdown, l as LinkClusterSummary, m as undirectedEdgeKey, n as JsonLdReport, o as LinkCandidateOptions, p as matchesClusterFilter, r as LiveHeadReport, s as LinkCandidatePair, t as resolveRouteLink, u as candidateSourceText, v as ContentCollectionOptions, w as llmsMarkdown, x as MarkdownLock, y as ContentEntry } from "./index-C5w9ripz.js";
5
+ import { a as inspectNode, c as renderRobots, i as contentSignal, l as renderSitemap, n as ProjectionConfig, o as isSitemapEligible, r as RobotsConfig, s as lastModified, t as NodeReport } from "./projections-Bk-qOOsR.js";
6
+ export { type Anchor, type AnchorRegion, type BodyEntry, type BuildSeoGraphInput, type CheckGraphOptions, type ContentCheckOptions, type ContentCollectionOptions, type ContentEntry, type ContentFinding, type ContentPolicy, type ContentRuleName, type CoverageRule, type FactDefinition, type Facts, type FreshnessEntry, type FreshnessPolicy, type FreshnessReport, type Heading, type InlineNode, type JsonLdDocument, type JsonLdNode, type JsonLdReport, type LinkCandidateOptions, type LinkCandidatePair, type LinkCandidateResult, type LinkClusterSummary, type LinkEdge, type LinkGraphDiff, type LiveHeadReport, type LlmsPage, type MarkdownLock, type MarkdownWriter, type Message, type NodeReport, type PageContent, type PageDocument, type PageHead, type PageHeadsOptions, type ProjectionConfig, type PublicPath, RENDERED_EDGE_ARTIFACT_SCHEMA_VERSION, type Register, type RenderedDocument, type RenderedEdgeArtifact, type RenderedEdgeArtifactCrawl, type RenderedEdgeFailure, type RenderedGraph, type RenderedPage, type RobotsConfig, type RouteSeo, type SectionDocument, type SectionItem, type SectionKind, type SeoCollection, type SeoEdge, type SeoEdgeType, type SeoFaq, type SeoGraph, type SeoGraphJson, type SeoInstance, type SeoKind, type SeoNode, type SeoNodeHead, type SeoPageHead, type SeoRouteNode, type SeoSource, type Severity, type SimpleEdge, type SitemapPolicy, type Violation, type VisualDocument, type WordCountRule, applyTitleTemplate, buildRenderedGraph, buildSeoGraph, candidateSourceText, checkCoverage, checkFreshness, checkGraph, checkPageContent, checkRenderedContent, checkRenderedCoverage, contentCollection, contentHash, contentSignal, createMarkdownLock, decodeRenderedEdgeArtifact, decodeRenderedEdges, defineFacts, defineSectionKind, diffLinkGraph, documentMarkdown, extractAnchors, extractPageContent, fact, freshnessReport, generateLinkCandidates, graphFromJson, graphToJson, hasBlockingIssues, hasStructuralViolations, hashDocument, inspectHtml, inspectNode, isSitemapEligible, lastModified, llmsMarkdown, llmsSection, matchesClusterFilter, normalizePath, pageHeads, renderRobots, renderSitemap, renderedCheckTargets, renderedGraphFromEdges, resolveFact, resolveRouteLink, sectionMarkdown, selectPageHeadNodes, undirectedEdgeKey };
package/dist/index.js CHANGED
@@ -1,10 +1,14 @@
1
1
  import { t as applyTitleTemplate } from "./declare-D5o7hwmv.js";
2
- import { t as buildSeoGraph } from "./graph-Bcsourlv.js";
2
+ import { t as buildSeoGraph } from "./graph-DMNOLXLu.js";
3
3
  import { n as graphToJson, t as graphFromJson } from "./wire-B4RQMc4e.js";
4
4
  import { i as lastModified, n as inspectNode, o as renderRobots, r as isSitemapEligible, s as renderSitemap, t as contentSignal } from "./projections-D0oOFKiY.js";
5
- import { i as checkRenderedCoverage, n as checkFreshness, o as hasStructuralViolations, r as checkGraph, s as freshnessReport, t as checkCoverage } from "./checks-uZk5Em9A.js";
6
- import { a as RENDERED_EDGE_ARTIFACT_SCHEMA_VERSION, c as diffLinkGraph, d as renderedGraphFromEdges, f as hasBlockingIssues, i as renderedCheckTargets, l as extractAnchors, n as checkRenderedContent, o as buildRenderedGraph, p as inspectHtml, r as extractPageContent, s as decodeRenderedEdgeArtifact, t as checkPageContent, u as normalizePath } from "./content-kcEnQdfi.js";
7
- import { n as selectPageHeadNodes, t as pageHeads } from "./page-heads-6y-CARRQ.js";
5
+ import { i as checkRenderedCoverage, n as checkFreshness, o as hasStructuralViolations, r as checkGraph, s as freshnessReport, t as checkCoverage } from "./checks-BS-aDXB_.js";
6
+ import { a as RENDERED_EDGE_ARTIFACT_SCHEMA_VERSION, c as diffLinkGraph, d as renderedGraphFromEdges, f as hasBlockingIssues, i as renderedCheckTargets, l as extractAnchors, n as checkRenderedContent, o as buildRenderedGraph, p as inspectHtml, r as extractPageContent, s as decodeRenderedEdgeArtifact, t as checkPageContent, u as normalizePath } from "./content-CHd0X4s6.js";
7
+ import { n as selectPageHeadNodes, t as pageHeads } from "./page-heads-BgbaIjj9.js";
8
+ import { o as hashDocument, s as contentHash } from "./inline-BbS93GAT.js";
9
+ import { i as resolveFact, n as defineFacts, r as fact } from "./facts-1OKclPOk.js";
10
+ import { n as documentMarkdown, r as sectionMarkdown, t as defineSectionKind } from "./markdown-C6dRE9zD.js";
11
+ import { n as llmsSection, t as llmsMarkdown } from "./llms-Dhr_5u2O.js";
8
12
  //#region src/core/collections.ts
9
13
  function contentCollection(options) {
10
14
  const entries = options.pages.map((page) => {
@@ -171,6 +175,34 @@ function resolveRouteLink(router, path) {
171
175
  return Object.values(routesById).find((route) => route.fullPath === path)?.options.staticData?.seo?.link;
172
176
  }
173
177
  //#endregion
174
- export { RENDERED_EDGE_ARTIFACT_SCHEMA_VERSION, applyTitleTemplate, buildRenderedGraph, buildSeoGraph, candidateSourceText, checkCoverage, checkFreshness, checkGraph, checkPageContent, checkRenderedContent, checkRenderedCoverage, contentCollection, contentSignal, decodeRenderedEdgeArtifact, decodeRenderedEdges, diffLinkGraph, extractAnchors, extractPageContent, freshnessReport, generateLinkCandidates, graphFromJson, graphToJson, hasBlockingIssues, hasStructuralViolations, inspectHtml, inspectNode, isSitemapEligible, lastModified, matchesClusterFilter, normalizePath, pageHeads, renderRobots, renderSitemap, renderedCheckTargets, renderedGraphFromEdges, resolveRouteLink, selectPageHeadNodes, undirectedEdgeKey };
178
+ //#region src/markdown/lock.ts
179
+ const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
180
+ /** Build the v1 lock without changing document hashes, message hashes, or sources. */
181
+ function createMarkdownLock(documents) {
182
+ const pages = Object.create(null);
183
+ for (const document of [...documents].sort((left, right) => compare(left.path, right.path))) {
184
+ if (Object.hasOwn(pages, document.path)) throw new Error(`Duplicate document page path "${document.path}"`);
185
+ if (!/^[0-9a-f]{64}$/.test(document.hash)) throw new Error(`Invalid document document hash "${document.hash}" on "${document.path}"`);
186
+ const messages = Object.create(null);
187
+ for (const message of [...document.messages].sort((left, right) => compare(left.hash, right.hash) || compare(left.source, right.source))) {
188
+ if (!/^[0-9a-f]{16}$/.test(message.hash)) throw new Error(`Invalid document message hash "${message.hash}" on "${document.path}"`);
189
+ if (!Object.hasOwn(messages, message.hash)) messages[message.hash] = message.source;
190
+ }
191
+ pages[document.path] = {
192
+ document: document.hash,
193
+ messages
194
+ };
195
+ }
196
+ for (const [path, page] of Object.entries(pages)) {
197
+ if (typeof page.document !== "string") throw new Error(`Expected string\n at ["pages"][${JSON.stringify(path)}]["document"]`);
198
+ for (const [hash, source] of Object.entries(page.messages)) if (typeof source !== "string") throw new Error(`Expected string\n at ["pages"][${JSON.stringify(path)}]["messages"][${JSON.stringify(hash)}]`);
199
+ }
200
+ return {
201
+ version: 1,
202
+ pages
203
+ };
204
+ }
205
+ //#endregion
206
+ export { RENDERED_EDGE_ARTIFACT_SCHEMA_VERSION, applyTitleTemplate, buildRenderedGraph, buildSeoGraph, candidateSourceText, checkCoverage, checkFreshness, checkGraph, checkPageContent, checkRenderedContent, checkRenderedCoverage, contentCollection, contentHash, contentSignal, createMarkdownLock, decodeRenderedEdgeArtifact, decodeRenderedEdges, defineFacts, defineSectionKind, diffLinkGraph, documentMarkdown, extractAnchors, extractPageContent, fact, freshnessReport, generateLinkCandidates, graphFromJson, graphToJson, hasBlockingIssues, hasStructuralViolations, hashDocument, inspectHtml, inspectNode, isSitemapEligible, lastModified, llmsMarkdown, llmsSection, matchesClusterFilter, normalizePath, pageHeads, renderRobots, renderSitemap, renderedCheckTargets, renderedGraphFromEdges, resolveFact, resolveRouteLink, sectionMarkdown, selectPageHeadNodes, undirectedEdgeKey };
175
207
 
176
208
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/core/collections.ts","../src/core/link-candidates.ts","../src/core/resolve-route-link.ts"],"sourcesContent":["/**\n * Content collections — pages a param route renders from content (MDX, a CMS,\n * a manifest) — in the shape the graph reads. Works with any source whose\n * pages carry a URL, which covers Fumadocs `loader().getPages()` directly.\n */\n\nimport type { PublicPath } from \"./declare\";\nimport type { SeoCollection, SeoEdge, SeoInstance, SeoSource } from \"./graph\";\n\n/** What a page contributes: its instance metadata plus the paths it links to deliberately. */\nexport interface ContentEntry extends Omit<SeoInstance, \"path\"> {\n /** Defaults to the page's `url`. */\n readonly path?: string | undefined;\n /** Deliberate cross-links, emitted as `related` edges from this page. */\n readonly related?: ReadonlyArray<string> | undefined;\n}\n\nexport interface ContentCollectionOptions<Page> {\n /** The param route these pages render through, e.g. `/blog/$slug`. */\n readonly route: PublicPath;\n /** The `source` label stamped on every node, e.g. `\"blog\"`. */\n readonly source: SeoSource;\n readonly pages: ReadonlyArray<Page>;\n /** Map one page to its entry; decode frontmatter here. */\n readonly entry: (page: Page) => ContentEntry;\n /** Edges beyond `related` (crumb parents, collection membership). */\n readonly edges?: ReadonlyArray<SeoEdge> | undefined;\n}\n\nexport function contentCollection<Page extends { readonly url: string }>(\n options: ContentCollectionOptions<Page>,\n): SeoCollection {\n const entries = options.pages.map((page) => {\n const entry = options.entry(page);\n return { ...entry, path: entry.path ?? page.url };\n });\n return {\n route: options.route,\n source: options.source,\n instances: entries.map(({ related: _related, ...instance }) => instance),\n edges: [\n ...entries.flatMap((entry) =>\n [...new Set(entry.related ?? [])].map((to): SeoEdge => ({ from: entry.path, to, type: \"related\" })),\n ),\n ...(options.edges ?? []),\n ],\n };\n}\n","/**\n * Pure candidate generation for contextual cross-links.\n *\n * Given the declared graph, enumerate `(source, destination)` pairs that are\n * *plausible* contextual links and *not already connected*. A pair is plausible\n * when the two pages sit in the same cluster: a shared top-level section (e.g.\n * `/blog/a` and `/blog/b` under `/blog`) or, for root-level pages that share no\n * section, the same `kind`. Only sitemap-eligible pages are candidates — a\n * noindex, redirect, or param template is not a link surface.\n *\n * The plan is a proposal, never an application: nothing here writes a\n * declaration. Effect- and framework-free so it runs in the CLI, a Worker, or a\n * test on the same graph.\n */\n\nimport type { SeoGraph, SeoNode } from \"./graph\";\nimport type { AnchorRegion, SimpleEdge } from \"./links\";\nimport { isSitemapEligible } from \"./projections\";\n\n/** One proposed contextual link, in the direction the link would be authored. */\nexport interface LinkCandidatePair {\n readonly source: string;\n readonly destination: string;\n /** The cluster the pair shares: a top-level section, or `kind:<kind>`. */\n readonly cluster: string;\n /** Why the pair is plausible, e.g. `same top-level section \"/blog\"`. */\n readonly reason: string;\n}\n\nexport interface LinkCandidateOptions {\n /** Maximum candidates to return after ordering (default 50). */\n readonly limit?: number;\n /** Restrict to these clusters; a filter may be a section or a bare kind. */\n readonly clusters?: ReadonlyArray<string>;\n /** Anchors already served in HTML. Only body-region edges exclude their own direction. */\n readonly renderedEdges?: ReadonlyArray<SimpleEdge & { readonly region?: AnchorRegion }>;\n}\n\n/** One cluster and how many candidate pairs it contributes (before `limit`). */\nexport interface LinkClusterSummary {\n readonly key: string;\n readonly candidates: number;\n}\n\nexport interface LinkCandidateResult {\n readonly candidates: ReadonlyArray<LinkCandidatePair>;\n /** Candidate pairs before `limit` was applied (after cluster filters). */\n readonly total: number;\n readonly truncated: boolean;\n readonly clusters: ReadonlyArray<LinkClusterSummary>;\n}\n\n/**\n * Canonical path key: query and hash stripped, trailing slashes removed, \"/\"\n * preserved. This is the same path key the graph and the rendered-link core\n * use, so a served anchor like `/blog/a?ref=nav` still matches the graph pair\n * `/blog/a`.\n */\nconst normalize = (path: string): string => {\n const pathname = path.split(/[?#]/, 1)[0] ?? \"\";\n const trimmed = pathname.replace(/\\/+$/, \"\");\n return trimmed === \"\" ? \"/\" : trimmed;\n};\n\n/** Directionless pair key, so an edge in either direction means \"connected\". */\nexport const undirectedEdgeKey = (from: string, to: string): string => {\n const [a, b] = [normalize(from), normalize(to)].sort();\n return `${a}\\u0000${b}`;\n};\n\nconst directedEdgeKey = (from: string, to: string): string => `${normalize(from)}\\u0000${normalize(to)}`;\n\n/** First path segment, or undefined for the root page. */\nconst topSegment = (path: string): string | undefined => {\n const segment = path.split(\"/\").filter(Boolean)[0];\n return segment === undefined || segment === \"\" ? undefined : segment;\n};\n\n/** A page with no nested segment (`/`, `/pricing`, `/blog`) is root-level. */\nconst isRootLevel = (path: string): boolean => path.split(\"/\").filter(Boolean).length <= 1;\n\n/**\n * The cluster a pair shares, or undefined when they share none. Section-first:\n * a shared top-level section wins. The kind fallback is local to root-level\n * pages, so it never pairs two nested pages from different sections.\n */\nexport const candidateClusterOf = (a: SeoNode, b: SeoNode): { key: string; reason: string } | undefined => {\n const aSegment = topSegment(a.path);\n const bSegment = topSegment(b.path);\n if (aSegment !== undefined && aSegment === bSegment) {\n return { key: aSegment, reason: `same top-level section \"/${aSegment}\"` };\n }\n if (isRootLevel(a.path) && isRootLevel(b.path) && a.kind === b.kind) {\n return { key: `kind:${a.kind}`, reason: `same kind \"${a.kind}\"` };\n }\n return undefined;\n};\n\n/** A `--cluster` filter matches a section by name, or a kind via its bare label. */\nexport const matchesClusterFilter = (cluster: string, filter: string): boolean => {\n const normalized = filter.replace(/^\\/+/, \"\").toLowerCase();\n const key = cluster.toLowerCase();\n return key === normalized || key === `kind:${normalized}`;\n};\n\n/** Human reason string for a candidate's source page, used when handing a plan to Jev. */\nexport const candidateSourceText = (node: SeoNode): string =>\n node.instance?.description?.trim() ||\n node.instance?.title?.trim() ||\n node.policy.link?.description?.trim() ||\n node.policy.link?.title?.trim() ||\n node.path;\n\n/**\n * Enumerate reviewable contextual-link candidates from the declared graph.\n *\n * Excluded: self-pairs, pages that are not sitemap-eligible, pairs already\n * declared as a `related` edge, and — when {@link LinkCandidateOptions.renderedEdges}\n * is supplied — pairs already rendered as an anchor. Queries, hashes, and\n * trailing slashes are normalized so both sides compare by the graph's path key.\n */\nexport const generateLinkCandidates = (\n graph: SeoGraph,\n options: LinkCandidateOptions = {},\n): LinkCandidateResult => {\n const limit = options.limit ?? 50;\n const filters = options.clusters ?? [];\n // Legacy bare edges have no region. Treat them as body links so a v1 edge dump\n // remains conservative; region-aware artifacts retain nav/header/footer edges.\n const rendered = new Set(\n (options.renderedEdges ?? []).filter((edge) => edge.region === undefined || edge.region === \"body\")\n .map((edge) => directedEdgeKey(edge.from, edge.to)),\n );\n const declared = new Set(\n graph.edges\n .filter((edge) => edge.type === \"related\")\n .map((edge) => directedEdgeKey(edge.from, edge.to)),\n );\n\n const eligible = [...graph.nodes.values()].filter(isSitemapEligible);\n\n const all: Array<LinkCandidatePair> = [];\n for (let i = 0; i < eligible.length; i++) {\n for (let j = i + 1; j < eligible.length; j++) {\n const a = eligible[i]!;\n const b = eligible[j]!;\n const cluster = candidateClusterOf(a, b);\n if (cluster === undefined) continue;\n if (filters.length > 0 && !filters.some((filter) => matchesClusterFilter(cluster.key, filter))) {\n continue;\n }\n for (const [source, destination] of [[a.path, b.path], [b.path, a.path]]) {\n const key = directedEdgeKey(source!, destination!);\n if (declared.has(key) || rendered.has(key)) continue;\n all.push({ source: source!, destination: destination!, cluster: cluster.key, reason: cluster.reason });\n }\n }\n }\n\n const compare = (x: LinkCandidatePair, y: LinkCandidatePair): number =>\n x.cluster < y.cluster\n ? -1\n : x.cluster > y.cluster\n ? 1\n : x.source < y.source\n ? -1\n : x.source > y.source\n ? 1\n : x.destination < y.destination\n ? -1\n : x.destination > y.destination\n ? 1\n : 0;\n all.sort(compare);\n\n const counts = new Map<string, number>();\n for (const pair of all) counts.set(pair.cluster, (counts.get(pair.cluster) ?? 0) + 1);\n\n const candidates = all.slice(0, limit);\n return {\n candidates,\n total: all.length,\n truncated: all.length > candidates.length,\n clusters: [...counts.entries()].map(([key, count]) => ({ key, candidates: count })),\n };\n};\n\n/**\n * Decode a rendered-edge dump supplied to the CLI: either a bare array of\n * `{ from, to }` edges, or an object carrying `edges` (or `internalEdges`).\n * Throws on any other shape — this is user input, not a provider payload.\n */\nexport const decodeRenderedEdges = (input: unknown): ReadonlyArray<SimpleEdge & { readonly region?: AnchorRegion }> => {\n const array =\n Array.isArray(input)\n ? input\n : input !== null && typeof input === \"object\" && Array.isArray((input as { edges?: unknown }).edges)\n ? (input as { edges: ReadonlyArray<unknown> }).edges\n : input !== null &&\n typeof input === \"object\" &&\n Array.isArray((input as { internalEdges?: unknown }).internalEdges)\n ? (input as { internalEdges: ReadonlyArray<unknown> }).internalEdges\n : undefined;\n\n if (array === undefined) {\n throw new Error(\n \"expected an array of { from, to } edges, or an object with an `edges` array\",\n );\n }\n\n return array.map((item) => {\n if (item === null || typeof item !== \"object\") {\n throw new Error(\"each rendered edge must be an object with string `from` and `to`\");\n }\n const { from, to, region } = item as { from?: unknown; to?: unknown; region?: unknown };\n if (typeof from !== \"string\" || typeof to !== \"string\") {\n throw new Error(\"each rendered edge must have string `from` and `to`\");\n }\n if (region !== undefined && ![\"nav\", \"header\", \"footer\", \"body\"].includes(region as string)) {\n throw new Error(\"rendered edge region must be nav, header, footer, or body\");\n }\n return region === undefined ? { from, to } : { from, to, region: region as AnchorRegion };\n });\n};\n","import type { AnyRoute, AnyRouter } from \"@tanstack/react-router\";\n\nimport type { RouteSeo } from \"./declare\";\n\ninterface RouteMaps {\n routesByPath: Record<string, AnyRoute | undefined>;\n routesById: Record<string, AnyRoute>;\n}\n\n/**\n * Resolve a route's declared `seo.link` card (title + description) by its full\n * path.\n *\n * `useRouter()` returns a Router typed to this app's exact route tree, so\n * `routesByPath` is keyed by the literal `FileRouteTypes[\"fullPaths\"]` union —\n * but callers here (declared `related` targets) hold arbitrary runtime path\n * strings, not that literal type. `routesByPath` and `routesById` are plain\n * Records on every Router instance regardless of which route tree it's\n * parameterized over, so this narrows to that structural shape once, at this\n * boundary, instead of threading an `AnyRoute` cast through every call site.\n */\nexport function resolveRouteLink(router: AnyRouter, path: string): RouteSeo[\"link\"] {\n const { routesByPath, routesById } = router as unknown as RouteMaps;\n\n const direct = routesByPath[path];\n if (direct) return direct.options.staticData?.seo?.link;\n\n const byFullPath = Object.values(routesById).find((route) => route.fullPath === path);\n return byFullPath?.options.staticData?.seo?.link;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/core/collections.ts","../src/core/link-candidates.ts","../src/core/resolve-route-link.ts","../src/markdown/lock.ts"],"sourcesContent":["/**\n * Content collections — pages a param route renders from content (MDX, a CMS,\n * a manifest) — in the shape the graph reads. Works with any source whose\n * pages carry a URL, which covers Fumadocs `loader().getPages()` directly.\n */\n\nimport type { PublicPath } from \"./declare\";\nimport type { SeoCollection, SeoEdge, SeoInstance, SeoSource } from \"./graph\";\n\n/** What a page contributes: its instance metadata plus the paths it links to deliberately. */\nexport interface ContentEntry extends Omit<SeoInstance, \"path\"> {\n /** Defaults to the page's `url`. */\n readonly path?: string | undefined;\n /** Deliberate cross-links, emitted as `related` edges from this page. */\n readonly related?: ReadonlyArray<string> | undefined;\n}\n\nexport interface ContentCollectionOptions<Page> {\n /** The param route these pages render through, e.g. `/blog/$slug`. */\n readonly route: PublicPath;\n /** The `source` label stamped on every node, e.g. `\"blog\"`. */\n readonly source: SeoSource;\n readonly pages: ReadonlyArray<Page>;\n /** Map one page to its entry; decode frontmatter here. */\n readonly entry: (page: Page) => ContentEntry;\n /** Edges beyond `related` (crumb parents, collection membership). */\n readonly edges?: ReadonlyArray<SeoEdge> | undefined;\n}\n\nexport function contentCollection<Page extends { readonly url: string }>(\n options: ContentCollectionOptions<Page>,\n): SeoCollection {\n const entries = options.pages.map((page) => {\n const entry = options.entry(page);\n return { ...entry, path: entry.path ?? page.url };\n });\n return {\n route: options.route,\n source: options.source,\n instances: entries.map(({ related: _related, ...instance }) => instance),\n edges: [\n ...entries.flatMap((entry) =>\n [...new Set(entry.related ?? [])].map((to): SeoEdge => ({ from: entry.path, to, type: \"related\" })),\n ),\n ...(options.edges ?? []),\n ],\n };\n}\n","/**\n * Pure candidate generation for contextual cross-links.\n *\n * Given the declared graph, enumerate `(source, destination)` pairs that are\n * *plausible* contextual links and *not already connected*. A pair is plausible\n * when the two pages sit in the same cluster: a shared top-level section (e.g.\n * `/blog/a` and `/blog/b` under `/blog`) or, for root-level pages that share no\n * section, the same `kind`. Only sitemap-eligible pages are candidates — a\n * noindex, redirect, or param template is not a link surface.\n *\n * The plan is a proposal, never an application: nothing here writes a\n * declaration. Effect- and framework-free so it runs in the CLI, a Worker, or a\n * test on the same graph.\n */\n\nimport type { SeoGraph, SeoNode } from \"./graph\";\nimport type { AnchorRegion, SimpleEdge } from \"./links\";\nimport { isSitemapEligible } from \"./projections\";\n\n/** One proposed contextual link, in the direction the link would be authored. */\nexport interface LinkCandidatePair {\n readonly source: string;\n readonly destination: string;\n /** The cluster the pair shares: a top-level section, or `kind:<kind>`. */\n readonly cluster: string;\n /** Why the pair is plausible, e.g. `same top-level section \"/blog\"`. */\n readonly reason: string;\n}\n\nexport interface LinkCandidateOptions {\n /** Maximum candidates to return after ordering (default 50). */\n readonly limit?: number;\n /** Restrict to these clusters; a filter may be a section or a bare kind. */\n readonly clusters?: ReadonlyArray<string>;\n /** Anchors already served in HTML. Only body-region edges exclude their own direction. */\n readonly renderedEdges?: ReadonlyArray<SimpleEdge & { readonly region?: AnchorRegion }>;\n}\n\n/** One cluster and how many candidate pairs it contributes (before `limit`). */\nexport interface LinkClusterSummary {\n readonly key: string;\n readonly candidates: number;\n}\n\nexport interface LinkCandidateResult {\n readonly candidates: ReadonlyArray<LinkCandidatePair>;\n /** Candidate pairs before `limit` was applied (after cluster filters). */\n readonly total: number;\n readonly truncated: boolean;\n readonly clusters: ReadonlyArray<LinkClusterSummary>;\n}\n\n/**\n * Canonical path key: query and hash stripped, trailing slashes removed, \"/\"\n * preserved. This is the same path key the graph and the rendered-link core\n * use, so a served anchor like `/blog/a?ref=nav` still matches the graph pair\n * `/blog/a`.\n */\nconst normalize = (path: string): string => {\n const pathname = path.split(/[?#]/, 1)[0] ?? \"\";\n const trimmed = pathname.replace(/\\/+$/, \"\");\n return trimmed === \"\" ? \"/\" : trimmed;\n};\n\n/** Directionless pair key, so an edge in either direction means \"connected\". */\nexport const undirectedEdgeKey = (from: string, to: string): string => {\n const [a, b] = [normalize(from), normalize(to)].sort();\n return `${a}\\u0000${b}`;\n};\n\nconst directedEdgeKey = (from: string, to: string): string => `${normalize(from)}\\u0000${normalize(to)}`;\n\n/** First path segment, or undefined for the root page. */\nconst topSegment = (path: string): string | undefined => {\n const segment = path.split(\"/\").filter(Boolean)[0];\n return segment === undefined || segment === \"\" ? undefined : segment;\n};\n\n/** A page with no nested segment (`/`, `/pricing`, `/blog`) is root-level. */\nconst isRootLevel = (path: string): boolean => path.split(\"/\").filter(Boolean).length <= 1;\n\n/**\n * The cluster a pair shares, or undefined when they share none. Section-first:\n * a shared top-level section wins. The kind fallback is local to root-level\n * pages, so it never pairs two nested pages from different sections.\n */\nexport const candidateClusterOf = (a: SeoNode, b: SeoNode): { key: string; reason: string } | undefined => {\n const aSegment = topSegment(a.path);\n const bSegment = topSegment(b.path);\n if (aSegment !== undefined && aSegment === bSegment) {\n return { key: aSegment, reason: `same top-level section \"/${aSegment}\"` };\n }\n if (isRootLevel(a.path) && isRootLevel(b.path) && a.kind === b.kind) {\n return { key: `kind:${a.kind}`, reason: `same kind \"${a.kind}\"` };\n }\n return undefined;\n};\n\n/** A `--cluster` filter matches a section by name, or a kind via its bare label. */\nexport const matchesClusterFilter = (cluster: string, filter: string): boolean => {\n const normalized = filter.replace(/^\\/+/, \"\").toLowerCase();\n const key = cluster.toLowerCase();\n return key === normalized || key === `kind:${normalized}`;\n};\n\n/** Human reason string for a candidate's source page, used when handing a plan to Jev. */\nexport const candidateSourceText = (node: SeoNode): string =>\n node.instance?.description?.trim() ||\n node.instance?.title?.trim() ||\n node.policy.link?.description?.trim() ||\n node.policy.link?.title?.trim() ||\n node.path;\n\n/**\n * Enumerate reviewable contextual-link candidates from the declared graph.\n *\n * Excluded: self-pairs, pages that are not sitemap-eligible, pairs already\n * declared as a `related` edge, and — when {@link LinkCandidateOptions.renderedEdges}\n * is supplied — pairs already rendered as an anchor. Queries, hashes, and\n * trailing slashes are normalized so both sides compare by the graph's path key.\n */\nexport const generateLinkCandidates = (\n graph: SeoGraph,\n options: LinkCandidateOptions = {},\n): LinkCandidateResult => {\n const limit = options.limit ?? 50;\n const filters = options.clusters ?? [];\n // Legacy bare edges have no region. Treat them as body links so a v1 edge dump\n // remains conservative; region-aware artifacts retain nav/header/footer edges.\n const rendered = new Set(\n (options.renderedEdges ?? []).filter((edge) => edge.region === undefined || edge.region === \"body\")\n .map((edge) => directedEdgeKey(edge.from, edge.to)),\n );\n const declared = new Set(\n graph.edges\n .filter((edge) => edge.type === \"related\")\n .map((edge) => directedEdgeKey(edge.from, edge.to)),\n );\n\n const eligible = [...graph.nodes.values()].filter(isSitemapEligible);\n\n const all: Array<LinkCandidatePair> = [];\n for (let i = 0; i < eligible.length; i++) {\n for (let j = i + 1; j < eligible.length; j++) {\n const a = eligible[i]!;\n const b = eligible[j]!;\n const cluster = candidateClusterOf(a, b);\n if (cluster === undefined) continue;\n if (filters.length > 0 && !filters.some((filter) => matchesClusterFilter(cluster.key, filter))) {\n continue;\n }\n for (const [source, destination] of [[a.path, b.path], [b.path, a.path]]) {\n const key = directedEdgeKey(source!, destination!);\n if (declared.has(key) || rendered.has(key)) continue;\n all.push({ source: source!, destination: destination!, cluster: cluster.key, reason: cluster.reason });\n }\n }\n }\n\n const compare = (x: LinkCandidatePair, y: LinkCandidatePair): number =>\n x.cluster < y.cluster\n ? -1\n : x.cluster > y.cluster\n ? 1\n : x.source < y.source\n ? -1\n : x.source > y.source\n ? 1\n : x.destination < y.destination\n ? -1\n : x.destination > y.destination\n ? 1\n : 0;\n all.sort(compare);\n\n const counts = new Map<string, number>();\n for (const pair of all) counts.set(pair.cluster, (counts.get(pair.cluster) ?? 0) + 1);\n\n const candidates = all.slice(0, limit);\n return {\n candidates,\n total: all.length,\n truncated: all.length > candidates.length,\n clusters: [...counts.entries()].map(([key, count]) => ({ key, candidates: count })),\n };\n};\n\n/**\n * Decode a rendered-edge dump supplied to the CLI: either a bare array of\n * `{ from, to }` edges, or an object carrying `edges` (or `internalEdges`).\n * Throws on any other shape — this is user input, not a provider payload.\n */\nexport const decodeRenderedEdges = (input: unknown): ReadonlyArray<SimpleEdge & { readonly region?: AnchorRegion }> => {\n const array =\n Array.isArray(input)\n ? input\n : input !== null && typeof input === \"object\" && Array.isArray((input as { edges?: unknown }).edges)\n ? (input as { edges: ReadonlyArray<unknown> }).edges\n : input !== null &&\n typeof input === \"object\" &&\n Array.isArray((input as { internalEdges?: unknown }).internalEdges)\n ? (input as { internalEdges: ReadonlyArray<unknown> }).internalEdges\n : undefined;\n\n if (array === undefined) {\n throw new Error(\n \"expected an array of { from, to } edges, or an object with an `edges` array\",\n );\n }\n\n return array.map((item) => {\n if (item === null || typeof item !== \"object\") {\n throw new Error(\"each rendered edge must be an object with string `from` and `to`\");\n }\n const { from, to, region } = item as { from?: unknown; to?: unknown; region?: unknown };\n if (typeof from !== \"string\" || typeof to !== \"string\") {\n throw new Error(\"each rendered edge must have string `from` and `to`\");\n }\n if (region !== undefined && ![\"nav\", \"header\", \"footer\", \"body\"].includes(region as string)) {\n throw new Error(\"rendered edge region must be nav, header, footer, or body\");\n }\n return region === undefined ? { from, to } : { from, to, region: region as AnchorRegion };\n });\n};\n","import type { AnyRoute, AnyRouter } from \"@tanstack/react-router\";\n\nimport type { RouteSeo } from \"./declare\";\n\ninterface RouteMaps {\n routesByPath: Record<string, AnyRoute | undefined>;\n routesById: Record<string, AnyRoute>;\n}\n\n/**\n * Resolve a route's declared `seo.link` card (title + description) by its full\n * path.\n *\n * `useRouter()` returns a Router typed to this app's exact route tree, so\n * `routesByPath` is keyed by the literal `FileRouteTypes[\"fullPaths\"]` union —\n * but callers here (declared `related` targets) hold arbitrary runtime path\n * strings, not that literal type. `routesByPath` and `routesById` are plain\n * Records on every Router instance regardless of which route tree it's\n * parameterized over, so this narrows to that structural shape once, at this\n * boundary, instead of threading an `AnyRoute` cast through every call site.\n */\nexport function resolveRouteLink(router: AnyRouter, path: string): RouteSeo[\"link\"] {\n const { routesByPath, routesById } = router as unknown as RouteMaps;\n\n const direct = routesByPath[path];\n if (direct) return direct.options.staticData?.seo?.link;\n\n const byFullPath = Object.values(routesById).find((route) => route.fullPath === path);\n return byFullPath?.options.staticData?.seo?.link;\n}\n","import type { PageDocument } from \"./document\";\n\n/** Stable, reviewable snapshot of document and message hashes with source locations. */\nexport interface MarkdownLock {\n readonly version: 1;\n readonly pages: Readonly<Record<string, {\n readonly document: string;\n readonly messages: Readonly<Record<string, string>>;\n }>>;\n}\n\nconst compare = (left: string, right: string): number => (left < right ? -1 : left > right ? 1 : 0);\n\n/** Build the v1 lock without changing document hashes, message hashes, or sources. */\nexport function createMarkdownLock(documents: ReadonlyArray<PageDocument>): MarkdownLock {\n const pages: Record<string, { document: string; messages: Record<string, string> }> =\n Object.create(null);\n\n for (const document of [...documents].sort((left, right) => compare(left.path, right.path))) {\n if (Object.hasOwn(pages, document.path))\n throw new Error(`Duplicate document page path \"${document.path}\"`);\n if (!/^[0-9a-f]{64}$/.test(document.hash))\n throw new Error(`Invalid document document hash \"${document.hash}\" on \"${document.path}\"`);\n\n const messages: Record<string, string> = Object.create(null);\n for (const message of [...document.messages].sort(\n (left, right) => compare(left.hash, right.hash) || compare(left.source, right.source),\n )) {\n if (!/^[0-9a-f]{16}$/.test(message.hash))\n throw new Error(`Invalid document message hash \"${message.hash}\" on \"${document.path}\"`);\n if (!Object.hasOwn(messages, message.hash)) messages[message.hash] = message.source;\n }\n pages[document.path] = { document: document.hash, messages };\n }\n\n // Typed callers supply strings, but the lock remains a validating runtime boundary.\n for (const [path, page] of Object.entries(pages)) {\n if (typeof page.document !== \"string\")\n throw new Error(`Expected string\\n at [\"pages\"][${JSON.stringify(path)}][\"document\"]`);\n for (const [hash, source] of Object.entries(page.messages)) {\n if (typeof source !== \"string\")\n throw new Error(`Expected string\\n at [\"pages\"][${JSON.stringify(path)}][\"messages\"][${JSON.stringify(hash)}]`);\n }\n }\n return { version: 1, pages };\n}\n"],"mappings":"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"}