mcp-scraper 0.3.14 → 0.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/api-server.cjs +1991 -508
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/browser-agent-stdio-server.cjs +6 -6
- package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
- package/dist/bin/browser-agent-stdio-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs +425 -44
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.js +6 -5
- package/dist/bin/mcp-scraper-combined-stdio-server.js.map +1 -1
- package/dist/bin/mcp-scraper-install.cjs +2 -2
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +419 -38
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/chunk-2MX5WOII.js +7 -0
- package/dist/chunk-2MX5WOII.js.map +1 -0
- package/dist/{chunk-DBQDG7EH.js → chunk-5H22TOXQ.js} +28 -1
- package/dist/chunk-5H22TOXQ.js.map +1 -0
- package/dist/{chunk-KPXMPAJ3.js → chunk-7Y6JDBML.js} +2 -2
- package/dist/chunk-7Y6JDBML.js.map +1 -0
- package/dist/{chunk-AX7UBYLG.js → chunk-WJ2LWHPA.js} +7 -7
- package/dist/chunk-WJ2LWHPA.js.map +1 -0
- package/dist/{chunk-HM6FHV5U.js → chunk-WQAIOY2D.js} +424 -39
- package/dist/chunk-WQAIOY2D.js.map +1 -0
- package/dist/{chunk-AZ5PKU4F.js → chunk-ZAUMSBV3.js} +2 -2
- package/dist/{db-BE4JVB3V.js → db-P76GVIFN.js} +2 -2
- package/dist/{server-ILIVSPNY.js → server-UOQ2JIUQ.js} +1106 -93
- package/dist/server-UOQ2JIUQ.js.map +1 -0
- package/dist/{worker-FG7ZWEGA.js → worker-2WVKKCC7.js} +3 -3
- package/docs/final-tooling-spec.md +206 -0
- package/docs/mcp-tool-design-guide.md +225 -0
- package/docs/mcp-tool-manifest.generated.json +13 -13
- package/docs/seo-crawl-report-spec.md +287 -0
- package/docs/tool-catalog-spec.md +388 -0
- package/package.json +2 -1
- package/dist/chunk-3QHZPR4U.js +0 -7
- package/dist/chunk-3QHZPR4U.js.map +0 -1
- package/dist/chunk-AX7UBYLG.js.map +0 -1
- package/dist/chunk-DBQDG7EH.js.map +0 -1
- package/dist/chunk-HM6FHV5U.js.map +0 -1
- package/dist/chunk-KPXMPAJ3.js.map +0 -1
- package/dist/server-ILIVSPNY.js.map +0 -1
- /package/dist/{chunk-AZ5PKU4F.js.map → chunk-ZAUMSBV3.js.map} +0 -0
- /package/dist/{db-BE4JVB3V.js.map → db-P76GVIFN.js.map} +0 -0
- /package/dist/{worker-FG7ZWEGA.js.map → worker-2WVKKCC7.js.map} +0 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# SEO Crawl Report — Screaming Frog parity spec (scrape-report scope)
|
|
2
|
+
|
|
3
|
+
Goal: from our `extract_site` crawl HTML, reproduce the most valuable ~50% of Screaming Frog's
|
|
4
|
+
per-URL crawl reports — **titles, metas, headings, indexability, canonicals, content, structured
|
|
5
|
+
data, and internal-link analysis** — and emit them as structured files in the bulk-scrape folder
|
|
6
|
+
plus an audit report.
|
|
7
|
+
|
|
8
|
+
Scope boundary (user directive): **scrape reports only** — per-URL crawl data and the link graph we
|
|
9
|
+
can derive from it. Out of scope here: JS-rendering diffs, log-file analysis, GA/GSC API joins,
|
|
10
|
+
spell-check, PageSpeed/Lighthouse, AMP validation, crawl scheduling.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 0. Current state (verified)
|
|
15
|
+
|
|
16
|
+
- `PageData` (`src/api/site-extractor.ts:11`): `url, status, via, title, metaDescription, h1,
|
|
17
|
+
headings[{level,text}], wordCount, schemaTypes[], canonicalUrl, internalLinks (count),
|
|
18
|
+
externalLinks (count), bodyMarkdown, schema[]`.
|
|
19
|
+
- `parsePageData(url, html, status, via)` (`src/api/site-extractor.ts:45`) — HTML only, **no response headers**.
|
|
20
|
+
- Fetch layers that DO have headers/timing:
|
|
21
|
+
- plain: `fetchAndParse` → `res: Response` (`res.headers`).
|
|
22
|
+
- rotating: `rotating-proxy-crawl.ts` `fetchBatch` → playwright `resp` (`resp.status()`, `resp.headers()`).
|
|
23
|
+
- `RotatingFetchResult` (`rotating-proxy-crawl.ts:5`) = `{ url, html, status, via }` — must be extended.
|
|
24
|
+
- Bulk output: `saveBulkSite(siteUrl, BulkPage[])` (`mcp-response-formatter.ts:68`); `BulkPage` only
|
|
25
|
+
carries `url,title,bodyMarkdown,metaDescription,schemaTypes`. Writes `index.md` + `pages/*.md`.
|
|
26
|
+
- `formatExtractSite` (`mcp-response-formatter.ts`) maps `pages` → `BulkPage` for the folder.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 1. Screaming Frog feature inventory → coverage decision
|
|
31
|
+
|
|
32
|
+
Legend: ✅ already captured · ◑ partial · ➕ ADD (in this spec) · 🔗 link-graph (Section 3) · ⛔ out of scope
|
|
33
|
+
|
|
34
|
+
### 1.1 Response / crawl
|
|
35
|
+
| SF data | Decision | Source |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| Address (URL) | ✅ | `PageData.url` |
|
|
38
|
+
| Status code + status text | ✅ | `PageData.status` |
|
|
39
|
+
| Content-Type | ➕ | response header |
|
|
40
|
+
| Response time (ms) | ➕ | fetch timing |
|
|
41
|
+
| Size (bytes, HTML transfer) | ➕ | `Content-Length` / `html.length` |
|
|
42
|
+
| Redirect URL + redirect type | ➕ | `Location` header / 3xx |
|
|
43
|
+
| Last-Modified | ➕ | response header |
|
|
44
|
+
| Crawl depth (clicks from start) | 🔗 | computed from link graph |
|
|
45
|
+
| Indexability + reason | ➕ | meta-robots + X-Robots + canonical + status |
|
|
46
|
+
|
|
47
|
+
### 1.2 On-page elements
|
|
48
|
+
| SF data | Decision | Source |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| Title 1 + length | ✅/➕ | have title; ADD length + pixel-width estimate |
|
|
51
|
+
| Title — pixel width | ➕ | char-width table estimate (flag approximate) |
|
|
52
|
+
| Meta description + length + pixel width | ✅/➕ | have text; ADD lengths |
|
|
53
|
+
| Meta keywords | ➕ | regex (low value, cheap) |
|
|
54
|
+
| H1 (1st + 2nd) + length | ✅/➕ | have headings[]; derive H1-1/H1-2 + length |
|
|
55
|
+
| H2 (1st/2nd) + count | ✅ | from headings[] |
|
|
56
|
+
| Word count | ✅ | `PageData.wordCount` |
|
|
57
|
+
| Text ratio (text/HTML) | ➕ | `bodyText.length / html.length` |
|
|
58
|
+
| Meta robots | ➕ | regex |
|
|
59
|
+
| X-Robots-Tag | ➕ | response header |
|
|
60
|
+
| Canonical link element | ✅ | `PageData.canonicalUrl` |
|
|
61
|
+
| rel=next / rel=prev | ➕ | regex |
|
|
62
|
+
| hreflang entries | ➕ | regex (list of {lang,href}) |
|
|
63
|
+
| Open Graph tags | ➕ | regex (og:title/description/image/type) |
|
|
64
|
+
| Twitter card tags | ➕ | regex |
|
|
65
|
+
| Mobile/AMP alternate | ➕ | regex `<link rel="amphtml">`, `alternate` |
|
|
66
|
+
|
|
67
|
+
### 1.3 Images
|
|
68
|
+
| SF data | Decision | Source |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| Image count | ➕ | count `<img>` |
|
|
71
|
+
| Images missing alt | ➕ | `<img>` without non-empty `alt` |
|
|
72
|
+
| Alt text over N chars | ➕ | alt length check |
|
|
73
|
+
| Image >100KB | ⛔ (needs per-asset fetch) | skip in v1 |
|
|
74
|
+
|
|
75
|
+
### 1.4 Structured data
|
|
76
|
+
| SF data | Decision | Source |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| Schema types present | ✅ | `PageData.schemaTypes` |
|
|
79
|
+
| Raw JSON-LD | ✅ | `PageData.schema` |
|
|
80
|
+
| Microdata / RDFa | ◑ | JSON-LD only in v1 |
|
|
81
|
+
| Validation errors | ◑ | shape checks only (not full Google validator) |
|
|
82
|
+
|
|
83
|
+
### 1.5 Content
|
|
84
|
+
| SF data | Decision | Source |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| Low content / thin pages | ➕ | wordCount threshold |
|
|
87
|
+
| Exact duplicates | ➕ | hash of normalized body |
|
|
88
|
+
| Near-duplicates | ◑ | simhash (v2) — exact-hash in v1 |
|
|
89
|
+
|
|
90
|
+
### 1.6 Links — **the high-value gap (user-flagged)**
|
|
91
|
+
| SF data | Decision | Source |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| Outlinks (count) | ✅ | `internalLinks`/`externalLinks` counts |
|
|
94
|
+
| Outlinks edge list (target, anchor, rel, position) | 🔗➕ | replace counts with edge capture |
|
|
95
|
+
| Inlinks + unique inlinks per URL | 🔗 | computed (invert outlinks) |
|
|
96
|
+
| Anchor text per inlink | 🔗 | from edges |
|
|
97
|
+
| Internal nofollow | 🔗➕ | `rel="nofollow"` on edge |
|
|
98
|
+
| Crawl depth | 🔗 | BFS from start over internal edges |
|
|
99
|
+
| Orphan URLs | 🔗 | in sitemap/crawl set but zero internal inlinks |
|
|
100
|
+
| Broken internal/external links | 🔗 | edge target status ∈ 4xx/5xx |
|
|
101
|
+
| Redirecting links | 🔗 | edge target status ∈ 3xx |
|
|
102
|
+
|
|
103
|
+
### 1.7 Issue filters (the actionable "reports" — computed cross-page)
|
|
104
|
+
Titles: missing, duplicate, >60 char/>561px, <30 char, multiple, same-as-H1.
|
|
105
|
+
Meta desc: missing, duplicate, >155/>985px, <70, multiple.
|
|
106
|
+
H1: missing, duplicate, multiple, >70 char.
|
|
107
|
+
H2: missing, multiple.
|
|
108
|
+
Canonical: missing, canonicalised (non-self), multiple, non-indexable canonical target.
|
|
109
|
+
Directives: noindex, nofollow.
|
|
110
|
+
Response: 3xx (incl. chains/loops via edges), 4xx broken, 5xx, blocked-by-robots.
|
|
111
|
+
URL: >115 char, uppercase, underscores, params, non-ASCII, duplicate.
|
|
112
|
+
Content: thin (<X words), exact duplicate (hash collision).
|
|
113
|
+
Images: missing alt.
|
|
114
|
+
Structured data: present-but-invalid (shape), missing on key templates.
|
|
115
|
+
Links: broken internal/external, redirected internal, orphan pages.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 2. Suggested data model (THE deliverable)
|
|
120
|
+
|
|
121
|
+
### 2.1 Extend `PageData` (`src/api/site-extractor.ts:11`)
|
|
122
|
+
Add these fields (keep existing):
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// response/header-derived (require fetch-layer plumbing, Section 3.2)
|
|
126
|
+
contentType: string | null
|
|
127
|
+
responseTimeMs: number | null
|
|
128
|
+
sizeBytes: number | null
|
|
129
|
+
redirectUrl: string | null // Location on 3xx
|
|
130
|
+
lastModified: string | null
|
|
131
|
+
xRobotsTag: string | null
|
|
132
|
+
|
|
133
|
+
// on-page additions
|
|
134
|
+
titleLength: number | null
|
|
135
|
+
titlePixels: number | null // estimate, approximate
|
|
136
|
+
metaDescLength: number | null
|
|
137
|
+
metaKeywords: string | null
|
|
138
|
+
h1_2: string | null // second H1 if present
|
|
139
|
+
h2Count: number
|
|
140
|
+
metaRobots: string | null
|
|
141
|
+
relNext: string | null
|
|
142
|
+
relPrev: string | null
|
|
143
|
+
hreflang: Array<{ lang: string; href: string }>
|
|
144
|
+
og: { title?: string; description?: string; image?: string; type?: string } | null
|
|
145
|
+
twitter: { card?: string; title?: string; description?: string } | null
|
|
146
|
+
ampHref: string | null
|
|
147
|
+
|
|
148
|
+
// images
|
|
149
|
+
imageCount: number
|
|
150
|
+
imagesMissingAlt: number
|
|
151
|
+
|
|
152
|
+
// content
|
|
153
|
+
textRatio: number // bodyText.length / html.length
|
|
154
|
+
contentHash: string // sha1 of normalized visible text
|
|
155
|
+
|
|
156
|
+
// indexability (derived, see 3.3)
|
|
157
|
+
indexable: boolean
|
|
158
|
+
indexabilityReason: string | null // 'noindex' | 'canonicalised' | 'non-200' | 'x-robots-noindex' | null
|
|
159
|
+
|
|
160
|
+
// LINKS — replace the two counts with the edge list
|
|
161
|
+
outlinks: Array<{ href: string; anchor: string; rel: string | null; internal: boolean }>
|
|
162
|
+
// keep internalLinks/externalLinks as derived counts for back-compat
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 2.2 Link edge + graph model (post-crawl, computed)
|
|
166
|
+
```ts
|
|
167
|
+
interface LinkEdge { from: string; to: string; anchor: string; rel: string | null; internal: boolean }
|
|
168
|
+
|
|
169
|
+
interface PageLinkMetrics {
|
|
170
|
+
url: string
|
|
171
|
+
inlinks: number
|
|
172
|
+
uniqueInlinks: number
|
|
173
|
+
outlinksInternal: number
|
|
174
|
+
outlinksExternal: number
|
|
175
|
+
crawlDepth: number | null // BFS hops from startUrl over internal 200 edges; null = orphan/unreachable
|
|
176
|
+
orphan: boolean // crawled/in-sitemap but 0 internal inlinks
|
|
177
|
+
topAnchors: string[] // most common inbound anchor texts
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 2.3 Structured outputs written into the bulk folder
|
|
182
|
+
Alongside `index.md` + `pages/*.md`, add:
|
|
183
|
+
- `pages.jsonl` — one JSON line per page = full extended `PageData` minus `bodyMarkdown`/`schema`
|
|
184
|
+
(those stay in `pages/*.md`). This is the "Internal" tab.
|
|
185
|
+
- `links.jsonl` — one `LinkEdge` per line (internal + external). The "All Outlinks" export.
|
|
186
|
+
- `link-metrics.jsonl` — one `PageLinkMetrics` per URL. The inlinks/depth/orphan view.
|
|
187
|
+
- `issues.json` — the computed issue filters (Section 1.7) as `{ issueKey: { count, urls[] } }`.
|
|
188
|
+
- `report.md` — human summary (counts per issue, top offenders) = the audit deliverable.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 3. Implementation blueprint (atomic)
|
|
193
|
+
|
|
194
|
+
### 3.1 Capture: extend `parsePageData` — `src/api/site-extractor.ts:45`
|
|
195
|
+
Signature change:
|
|
196
|
+
```ts
|
|
197
|
+
function parsePageData(
|
|
198
|
+
url: string, html: string, status: number, via: 'fetch'|'browser',
|
|
199
|
+
resp?: { headers?: Record<string,string>; responseTimeMs?: number; redirectUrl?: string|null }
|
|
200
|
+
): PageData
|
|
201
|
+
```
|
|
202
|
+
Add, inside the function, regex/derivations:
|
|
203
|
+
- `titleLength = title?.length`; `titlePixels = estimatePixels(title)` (new helper, char-width table).
|
|
204
|
+
- `metaDescLength`, `metaKeywords` via `<meta name="keywords">`.
|
|
205
|
+
- `h1_2`/`h2Count` from existing `headings` array.
|
|
206
|
+
- `metaRobots` via `<meta name="robots" content="...">`.
|
|
207
|
+
- `relNext`/`relPrev` via `<link rel="next|prev">`.
|
|
208
|
+
- `hreflang[]` via `<link rel="alternate" hreflang="..">`.
|
|
209
|
+
- `og`/`twitter` via `<meta property="og:.."|name="twitter:..">`.
|
|
210
|
+
- `ampHref` via `<link rel="amphtml">`.
|
|
211
|
+
- `imageCount` = matches of `<img`; `imagesMissingAlt` = `<img>` lacking non-empty `alt`.
|
|
212
|
+
- `textRatio = bodyText.length / Math.max(1, html.length)`.
|
|
213
|
+
- `contentHash = sha1(bodyText.replace(/\s+/g,' ').trim())` (`node:crypto`).
|
|
214
|
+
- header fields from `resp`: `contentType, xRobotsTag, lastModified, sizeBytes, responseTimeMs, redirectUrl`.
|
|
215
|
+
- `outlinks[]`: replace the count-only loop (`site-extractor.ts:102-110`) — for each `<a href>` also
|
|
216
|
+
capture anchor text and `rel`; classify `internal` by origin; keep `internalLinks`/`externalLinks`
|
|
217
|
+
as `.filter().length` for back-compat.
|
|
218
|
+
- `indexable`/`indexabilityReason`: `false` if status!=200, or metaRobots/xRobots contains `noindex`,
|
|
219
|
+
or canonical present and != self → reason set accordingly.
|
|
220
|
+
|
|
221
|
+
New helper `estimatePixels(s)` — sum per-char widths from a static map (approx Arial 'M'≈14, 'i'≈4…);
|
|
222
|
+
mark all pixel fields "approximate" in docs. ~25 lines.
|
|
223
|
+
|
|
224
|
+
### 3.2 Plumb headers/timing into both fetch paths
|
|
225
|
+
- `RotatingFetchResult` (`rotating-proxy-crawl.ts:5`): add
|
|
226
|
+
`headers?: Record<string,string>; responseTimeMs?: number; redirectUrl?: string|null`.
|
|
227
|
+
In `fetchBatch` (`:60-98`): time the `goto`, `resp.headers()`, capture `Location` when 3xx.
|
|
228
|
+
- `fetchAndParse` (`site-extractor.ts:115`): build the same `resp` object from `res.headers` + timing,
|
|
229
|
+
pass as 5th arg to `parsePageData`.
|
|
230
|
+
- In `extractSite` rotating branch (`site-extractor.ts:~185`): pass `r.headers/responseTimeMs/redirectUrl`
|
|
231
|
+
into `parsePageData(r.url, r.html, r.status, 'browser', {...})`.
|
|
232
|
+
|
|
233
|
+
### 3.3 Post-crawl link graph — new file `src/api/seo-link-graph.ts`
|
|
234
|
+
```ts
|
|
235
|
+
export function buildLinkGraph(pages: PageData[], startUrl: string):
|
|
236
|
+
{ edges: LinkEdge[]; metrics: Map<string, PageLinkMetrics> }
|
|
237
|
+
```
|
|
238
|
+
Logic:
|
|
239
|
+
- Flatten `pages[].outlinks` → `edges` (from = page.url).
|
|
240
|
+
- Build `inlinks` map by inverting internal edges; `uniqueInlinks` = distinct `from`.
|
|
241
|
+
- `crawlDepth`: BFS from `startUrl` over internal edges whose target status==200; unreached → null.
|
|
242
|
+
- `orphan`: page in set with 0 internal inlinks and url != startUrl.
|
|
243
|
+
- `topAnchors`: top-3 inbound anchors by frequency.
|
|
244
|
+
- Status join: map target→status from `pages` to flag broken (4xx/5xx) / redirect (3xx) edges.
|
|
245
|
+
|
|
246
|
+
### 3.4 Issue computation — new file `src/api/seo-issues.ts`
|
|
247
|
+
```ts
|
|
248
|
+
export function computeIssues(pages: PageData[], metrics: Map<string,PageLinkMetrics>):
|
|
249
|
+
Record<string, { count: number; urls: string[] }>
|
|
250
|
+
```
|
|
251
|
+
Thresholds (SF defaults): title >60/<30 char & >561px; meta >155/<70 & >985px; H1 >70; URL >115;
|
|
252
|
+
thin <200 words (configurable). Duplicates: group by exact `title`/`metaDescription`/`contentHash`,
|
|
253
|
+
flag groups size>1. Broken/redirect/orphan from `metrics`/edges. Indexability from `PageData`.
|
|
254
|
+
|
|
255
|
+
### 3.5 Write structured outputs — extend `saveBulkSite` (`mcp-response-formatter.ts:68`)
|
|
256
|
+
- Widen `BulkPage` → accept full extended `PageData` (or pass `PageData[]` directly).
|
|
257
|
+
- After writing `pages/*.md`, also `writeFileSync`:
|
|
258
|
+
- `pages.jsonl` (PageData minus bodyMarkdown/schema),
|
|
259
|
+
- `links.jsonl`, `link-metrics.jsonl`, `issues.json`, `report.md`.
|
|
260
|
+
- `formatExtractSite` already has full `pages`; pass them through (today it maps to the slim BulkPage —
|
|
261
|
+
change to pass the full objects, call `buildLinkGraph` + `computeIssues` before `saveBulkSite`).
|
|
262
|
+
- Return extra paths in the bulk summary + `structuredContent` (e.g. `pagesJsonl`, `issuesFile`).
|
|
263
|
+
|
|
264
|
+
### 3.6 The skill — `seo-crawl-audit`
|
|
265
|
+
Orchestrator (no new server code):
|
|
266
|
+
1. Call `extract_site(url, rotateProxies:true)` → folder path from response.
|
|
267
|
+
2. Read `pages.jsonl` + `issues.json` + `link-metrics.jsonl` from the folder.
|
|
268
|
+
3. Emit a prioritized SEO report: response-code breakdown, title/meta/H1 issues with offender lists,
|
|
269
|
+
indexability summary, thin/duplicate content, structured-data coverage, and the internal-link
|
|
270
|
+
section (orphans, deepest pages, most-linked, broken internal links).
|
|
271
|
+
4. Hand the full link graph to the existing `site-architecture-auditor` skill for equity/architecture
|
|
272
|
+
scoring (it already builds this graph from SF exports — we now feed it ours).
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## 4. Build order (phased, each independently shippable)
|
|
277
|
+
1. **P1 — page fields (no headers):** 3.1 minus header fields + 3.5 `pages.jsonl`. Unlocks titles/metas/
|
|
278
|
+
H1/canonical/indexability(meta)/thin/dup/schema reports immediately.
|
|
279
|
+
2. **P2 — link graph:** 3.1 outlinks edge capture + 3.3 + `links.jsonl`/`link-metrics.jsonl`. Unlocks
|
|
280
|
+
inlinks/depth/orphans/broken-links — the user's headline ask.
|
|
281
|
+
3. **P3 — headers/timing:** 3.2 (both fetch paths). Adds content-type/size/response-time/X-Robots/redirects.
|
|
282
|
+
4. **P4 — issues + report:** 3.4 + `issues.json`/`report.md`.
|
|
283
|
+
5. **P5 — skill:** 3.6 + handoff to `site-architecture-auditor`.
|
|
284
|
+
|
|
285
|
+
## 5. Explicit non-goals (v1)
|
|
286
|
+
Per-asset image weight, microdata/RDFa, full structured-data validation, near-duplicate simhash,
|
|
287
|
+
JS-render diffing, pixel-width exactness (estimate only), redirect-chain hop-by-hop beyond one hop.
|