@houtini/seo-audit-console 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +92 -0
- package/README.md +211 -0
- package/dist/audit/checks.d.ts +35 -0
- package/dist/audit/checks.d.ts.map +1 -0
- package/dist/audit/checks.js +1475 -0
- package/dist/audit/checks.js.map +1 -0
- package/dist/audit/drift.d.ts +40 -0
- package/dist/audit/drift.d.ts.map +1 -0
- package/dist/audit/drift.js +148 -0
- package/dist/audit/drift.js.map +1 -0
- package/dist/audit/engine.d.ts +33 -0
- package/dist/audit/engine.d.ts.map +1 -0
- package/dist/audit/engine.js +186 -0
- package/dist/audit/engine.js.map +1 -0
- package/dist/audit/opportunities.d.ts +23 -0
- package/dist/audit/opportunities.d.ts.map +1 -0
- package/dist/audit/opportunities.js +149 -0
- package/dist/audit/opportunities.js.map +1 -0
- package/dist/audit/report.d.ts +11 -0
- package/dist/audit/report.d.ts.map +1 -0
- package/dist/audit/report.js +59 -0
- package/dist/audit/report.js.map +1 -0
- package/dist/audit/schema-validate.d.ts +35 -0
- package/dist/audit/schema-validate.d.ts.map +1 -0
- package/dist/audit/schema-validate.js +293 -0
- package/dist/audit/schema-validate.js.map +1 -0
- package/dist/audit/templates.d.ts +43 -0
- package/dist/audit/templates.d.ts.map +1 -0
- package/dist/audit/templates.js +129 -0
- package/dist/audit/templates.js.map +1 -0
- package/dist/audit/topicGaps.d.ts +42 -0
- package/dist/audit/topicGaps.d.ts.map +1 -0
- package/dist/audit/topicGaps.js +181 -0
- package/dist/audit/topicGaps.js.map +1 -0
- package/dist/core/AuditDatabase.d.ts +33 -0
- package/dist/core/AuditDatabase.d.ts.map +1 -0
- package/dist/core/AuditDatabase.js +481 -0
- package/dist/core/AuditDatabase.js.map +1 -0
- package/dist/core/Backlinks.d.ts +33 -0
- package/dist/core/Backlinks.d.ts.map +1 -0
- package/dist/core/Backlinks.js +110 -0
- package/dist/core/Backlinks.js.map +1 -0
- package/dist/core/Crawler.d.ts +23 -0
- package/dist/core/Crawler.d.ts.map +1 -0
- package/dist/core/Crawler.js +588 -0
- package/dist/core/Crawler.js.map +1 -0
- package/dist/core/DataForSeoClient.d.ts +86 -0
- package/dist/core/DataForSeoClient.d.ts.map +1 -0
- package/dist/core/DataForSeoClient.js +232 -0
- package/dist/core/DataForSeoClient.js.map +1 -0
- package/dist/core/Entities.d.ts +23 -0
- package/dist/core/Entities.d.ts.map +1 -0
- package/dist/core/Entities.js +62 -0
- package/dist/core/Entities.js.map +1 -0
- package/dist/core/GscClient.d.ts +22 -0
- package/dist/core/GscClient.d.ts.map +1 -0
- package/dist/core/GscClient.js +93 -0
- package/dist/core/GscClient.js.map +1 -0
- package/dist/core/GscSync.d.ts +20 -0
- package/dist/core/GscSync.d.ts.map +1 -0
- package/dist/core/GscSync.js +133 -0
- package/dist/core/GscSync.js.map +1 -0
- package/dist/core/JobManager.d.ts +30 -0
- package/dist/core/JobManager.d.ts.map +1 -0
- package/dist/core/JobManager.js +68 -0
- package/dist/core/JobManager.js.map +1 -0
- package/dist/core/RankTracker.d.ts +25 -0
- package/dist/core/RankTracker.d.ts.map +1 -0
- package/dist/core/RankTracker.js +78 -0
- package/dist/core/RankTracker.js.map +1 -0
- package/dist/core/Refresh.d.ts +32 -0
- package/dist/core/Refresh.d.ts.map +1 -0
- package/dist/core/Refresh.js +73 -0
- package/dist/core/Refresh.js.map +1 -0
- package/dist/core/UrlInspector.d.ts +22 -0
- package/dist/core/UrlInspector.d.ts.map +1 -0
- package/dist/core/UrlInspector.js +92 -0
- package/dist/core/UrlInspector.js.map +1 -0
- package/dist/core/WikidataClient.d.ts +19 -0
- package/dist/core/WikidataClient.d.ts.map +1 -0
- package/dist/core/WikidataClient.js +59 -0
- package/dist/core/WikidataClient.js.map +1 -0
- package/dist/core/agentReadiness.d.ts +34 -0
- package/dist/core/agentReadiness.d.ts.map +1 -0
- package/dist/core/agentReadiness.js +119 -0
- package/dist/core/agentReadiness.js.map +1 -0
- package/dist/core/ctrModel.d.ts +2 -0
- package/dist/core/ctrModel.d.ts.map +1 -0
- package/dist/core/ctrModel.js +6 -0
- package/dist/core/ctrModel.js.map +1 -0
- package/dist/core/dashboardData.d.ts +312 -0
- package/dist/core/dashboardData.d.ts.map +1 -0
- package/dist/core/dashboardData.js +550 -0
- package/dist/core/dashboardData.js.map +1 -0
- package/dist/core/dataStorage.d.ts +42 -0
- package/dist/core/dataStorage.d.ts.map +1 -0
- package/dist/core/dataStorage.js +193 -0
- package/dist/core/dataStorage.js.map +1 -0
- package/dist/core/draftBrief.d.ts +27 -0
- package/dist/core/draftBrief.d.ts.map +1 -0
- package/dist/core/draftBrief.js +69 -0
- package/dist/core/draftBrief.js.map +1 -0
- package/dist/core/extract.d.ts +67 -0
- package/dist/core/extract.d.ts.map +1 -0
- package/dist/core/extract.js +262 -0
- package/dist/core/extract.js.map +1 -0
- package/dist/core/gscFreshness.d.ts +18 -0
- package/dist/core/gscFreshness.d.ts.map +1 -0
- package/dist/core/gscFreshness.js +32 -0
- package/dist/core/gscFreshness.js.map +1 -0
- package/dist/core/linkGraph.d.ts +19 -0
- package/dist/core/linkGraph.d.ts.map +1 -0
- package/dist/core/linkGraph.js +125 -0
- package/dist/core/linkGraph.js.map +1 -0
- package/dist/core/passageScore.d.ts +19 -0
- package/dist/core/passageScore.d.ts.map +1 -0
- package/dist/core/passageScore.js +59 -0
- package/dist/core/passageScore.js.map +1 -0
- package/dist/core/paths.d.ts +5 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +15 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/queryData.d.ts +35 -0
- package/dist/core/queryData.d.ts.map +1 -0
- package/dist/core/queryData.js +200 -0
- package/dist/core/queryData.js.map +1 -0
- package/dist/core/reranker.d.ts +8 -0
- package/dist/core/reranker.d.ts.map +1 -0
- package/dist/core/reranker.js +68 -0
- package/dist/core/reranker.js.map +1 -0
- package/dist/core/robots.d.ts +11 -0
- package/dist/core/robots.d.ts.map +1 -0
- package/dist/core/robots.js +76 -0
- package/dist/core/robots.js.map +1 -0
- package/dist/core/sitemap.d.ts +15 -0
- package/dist/core/sitemap.d.ts.map +1 -0
- package/dist/core/sitemap.js +100 -0
- package/dist/core/sitemap.js.map +1 -0
- package/dist/core/sql.d.ts +6 -0
- package/dist/core/sql.d.ts.map +1 -0
- package/dist/core/sql.js +6 -0
- package/dist/core/sql.js.map +1 -0
- package/dist/core/types.d.ts +22 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/url-key.d.ts +39 -0
- package/dist/core/url-key.d.ts.map +1 -0
- package/dist/core/url-key.js +106 -0
- package/dist/core/url-key.js.map +1 -0
- package/dist/generators/index.d.ts +45 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +184 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +1387 -0
- package/dist/server.js.map +1 -0
- package/dist/src/ui/dashboard.html +347 -0
- package/dist/src/ui/sync-progress.html +104 -0
- package/package.json +101 -0
- package/server.json +57 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** How many recent audit runs / crawl snapshots clear-crawl-history keeps. */
|
|
2
|
+
export declare const KEEP_RECENT = 5;
|
|
3
|
+
export interface PropertyStorage {
|
|
4
|
+
file: string;
|
|
5
|
+
siteUrl: string | null;
|
|
6
|
+
bytes: number;
|
|
7
|
+
searchAnalytics: number;
|
|
8
|
+
pages: number;
|
|
9
|
+
links: number;
|
|
10
|
+
pageSnapshots: number;
|
|
11
|
+
findings: number;
|
|
12
|
+
lastSynced: string | null;
|
|
13
|
+
lastCrawl: string | null;
|
|
14
|
+
}
|
|
15
|
+
export interface StorageSummary {
|
|
16
|
+
dataDir: string;
|
|
17
|
+
properties: PropertyStorage[];
|
|
18
|
+
caches: {
|
|
19
|
+
file: string;
|
|
20
|
+
bytes: number;
|
|
21
|
+
}[];
|
|
22
|
+
other: {
|
|
23
|
+
file: string;
|
|
24
|
+
bytes: number;
|
|
25
|
+
}[];
|
|
26
|
+
reportsBytes: number;
|
|
27
|
+
totalBytes: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function storageSummary(dataDir: string, siteUrl?: string): StorageSummary;
|
|
30
|
+
export declare const fmtBytes: (n: number) => string;
|
|
31
|
+
export interface PruneResult {
|
|
32
|
+
ok: boolean;
|
|
33
|
+
refused?: string;
|
|
34
|
+
action: string;
|
|
35
|
+
siteUrl: string;
|
|
36
|
+
detail: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
export declare function pruneProperty(dataDir: string, siteUrl: string, action: 'vacuum' | 'clear-crawl-history' | 'delete-property', confirm: boolean, runningJobs: {
|
|
39
|
+
id: string;
|
|
40
|
+
type: string;
|
|
41
|
+
}[]): PruneResult;
|
|
42
|
+
//# sourceMappingURL=dataStorage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataStorage.d.ts","sourceRoot":"","sources":["../../src/core/dataStorage.ts"],"names":[],"mappings":"AAWA,8EAA8E;AAC9E,eAAO,MAAM,WAAW,IAAI,CAAC;AAI7B,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AA0BD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,cAAc,CAiDhF;AAED,eAAO,MAAM,QAAQ,GAAI,GAAG,MAAM,KAAG,MAKpC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,QAAQ,GAAG,qBAAqB,GAAG,iBAAiB,EAC5D,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,GAC1C,WAAW,CAsEb"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dataStorage — data-hygiene: what's on disk per property (sizes, row counts, last
|
|
3
|
+
* sync/crawl), plus explicit prune actions (vacuum / clear-crawl-history /
|
|
4
|
+
* delete-property). Destructive actions require confirm:true and refuse loudly,
|
|
5
|
+
* naming exactly what would be deleted. Never touches a DB while a job is running.
|
|
6
|
+
*/
|
|
7
|
+
import Database from 'better-sqlite3';
|
|
8
|
+
import { readdirSync, statSync, existsSync, rmSync } from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { dbPathFor, sanitizeProperty } from './paths.js';
|
|
11
|
+
/** How many recent audit runs / crawl snapshots clear-crawl-history keeps. */
|
|
12
|
+
export const KEEP_RECENT = 5;
|
|
13
|
+
const CACHE_DB_NAMES = new Set(['dataforseo-cache.db', 'wikidata-cache.db']);
|
|
14
|
+
const sizeWithSidecars = (dbFile) => {
|
|
15
|
+
let total = 0;
|
|
16
|
+
for (const suffix of ['', '-wal', '-shm']) {
|
|
17
|
+
try {
|
|
18
|
+
total += statSync(dbFile + suffix).size;
|
|
19
|
+
}
|
|
20
|
+
catch { /* absent */ }
|
|
21
|
+
}
|
|
22
|
+
return total;
|
|
23
|
+
};
|
|
24
|
+
const dirSize = (dir) => {
|
|
25
|
+
let total = 0;
|
|
26
|
+
try {
|
|
27
|
+
for (const f of readdirSync(dir, { withFileTypes: true })) {
|
|
28
|
+
const p = path.join(dir, f.name);
|
|
29
|
+
if (f.isDirectory())
|
|
30
|
+
total += dirSize(p);
|
|
31
|
+
else {
|
|
32
|
+
try {
|
|
33
|
+
total += statSync(p).size;
|
|
34
|
+
}
|
|
35
|
+
catch { /* raced */ }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch { /* absent */ }
|
|
40
|
+
return total;
|
|
41
|
+
};
|
|
42
|
+
const safeCount = (db, table) => {
|
|
43
|
+
try {
|
|
44
|
+
return db.prepare(`SELECT COUNT(*) n FROM "${table}"`).get().n;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
export function storageSummary(dataDir, siteUrl) {
|
|
51
|
+
const properties = [];
|
|
52
|
+
const caches = [];
|
|
53
|
+
const other = [];
|
|
54
|
+
let files = [];
|
|
55
|
+
try {
|
|
56
|
+
files = readdirSync(dataDir).filter(f => f.endsWith('.db'));
|
|
57
|
+
}
|
|
58
|
+
catch { /* dataDir absent */ }
|
|
59
|
+
const wantStem = siteUrl ? sanitizeProperty(siteUrl) : null;
|
|
60
|
+
for (const file of files.sort()) {
|
|
61
|
+
const full = path.join(dataDir, file);
|
|
62
|
+
const bytes = sizeWithSidecars(full);
|
|
63
|
+
if (CACHE_DB_NAMES.has(file)) {
|
|
64
|
+
caches.push({ file, bytes });
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
let db = null;
|
|
68
|
+
try {
|
|
69
|
+
db = new Database(full, { readonly: true, fileMustExist: true });
|
|
70
|
+
const isProperty = !!db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='property_meta'`).get();
|
|
71
|
+
if (!isProperty) {
|
|
72
|
+
other.push({ file, bytes });
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const meta = db.prepare('SELECT site_url, last_synced_at FROM property_meta ORDER BY id LIMIT 1').get();
|
|
76
|
+
if (wantStem && file !== `${wantStem}.db`)
|
|
77
|
+
continue;
|
|
78
|
+
let lastCrawl = null;
|
|
79
|
+
try {
|
|
80
|
+
lastCrawl = db.prepare(`SELECT MAX(COALESCE(finished_at, started_at)) t FROM crawl_metadata`).get().t;
|
|
81
|
+
}
|
|
82
|
+
catch { /* no crawl table */ }
|
|
83
|
+
properties.push({
|
|
84
|
+
file,
|
|
85
|
+
siteUrl: meta?.site_url ?? null,
|
|
86
|
+
bytes,
|
|
87
|
+
searchAnalytics: safeCount(db, 'search_analytics'),
|
|
88
|
+
pages: safeCount(db, 'pages'),
|
|
89
|
+
links: safeCount(db, 'links'),
|
|
90
|
+
pageSnapshots: safeCount(db, 'page_snapshots'),
|
|
91
|
+
findings: safeCount(db, 'findings'),
|
|
92
|
+
lastSynced: meta?.last_synced_at ?? null,
|
|
93
|
+
lastCrawl,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
other.push({ file, bytes });
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
db?.close();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const reportsBytes = dirSize(path.join(dataDir, 'reports'));
|
|
104
|
+
const totalBytes = properties.reduce((s, p) => s + p.bytes, 0)
|
|
105
|
+
+ caches.reduce((s, c) => s + c.bytes, 0)
|
|
106
|
+
+ other.reduce((s, o) => s + o.bytes, 0)
|
|
107
|
+
+ reportsBytes;
|
|
108
|
+
return { dataDir, properties, caches, other, reportsBytes, totalBytes };
|
|
109
|
+
}
|
|
110
|
+
export const fmtBytes = (n) => {
|
|
111
|
+
if (n >= 1024 ** 3)
|
|
112
|
+
return `${(n / 1024 ** 3).toFixed(2)} GB`;
|
|
113
|
+
if (n >= 1024 ** 2)
|
|
114
|
+
return `${(n / 1024 ** 2).toFixed(1)} MB`;
|
|
115
|
+
if (n >= 1024)
|
|
116
|
+
return `${(n / 1024).toFixed(0)} KB`;
|
|
117
|
+
return `${n} B`;
|
|
118
|
+
};
|
|
119
|
+
export function pruneProperty(dataDir, siteUrl, action, confirm, runningJobs) {
|
|
120
|
+
const base = { action, siteUrl };
|
|
121
|
+
if (runningJobs.length) {
|
|
122
|
+
return {
|
|
123
|
+
...base, ok: false, detail: { runningJobs },
|
|
124
|
+
refused: `Refusing to ${action} while ${runningJobs.length} job(s) are running (${runningJobs.map(j => `${j.type}#${j.id}`).join(', ')}) — a prune mid-sync/crawl can corrupt or race the write. Wait for them to finish and retry.`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const dbFile = dbPathFor(dataDir, siteUrl);
|
|
128
|
+
if (!existsSync(dbFile)) {
|
|
129
|
+
return { ...base, ok: false, refused: `No database for ${siteUrl} at ${dbFile} — nothing to ${action}. Check list via data_storage (no args).`, detail: { dbFile } };
|
|
130
|
+
}
|
|
131
|
+
const bytesBefore = sizeWithSidecars(dbFile);
|
|
132
|
+
if (action === 'delete-property') {
|
|
133
|
+
const summary = storageSummary(dataDir, siteUrl).properties[0];
|
|
134
|
+
if (!confirm) {
|
|
135
|
+
return {
|
|
136
|
+
...base, ok: false, detail: { dbFile, bytes: bytesBefore, rows: summary },
|
|
137
|
+
refused: `REFUSED — delete-property is destructive and needs confirm:true. This would permanently delete ${dbFile} (+ -wal/-shm), ${fmtBytes(bytesBefore)}: ` +
|
|
138
|
+
`${summary?.searchAnalytics ?? '?'} search_analytics rows, ${summary?.pages ?? '?'} pages, ${summary?.links ?? '?'} links, ${summary?.pageSnapshots ?? '?'} snapshots, ${summary?.findings ?? '?'} findings. ` +
|
|
139
|
+
`GSC history is re-syncable but slow to rebuild. Re-run with confirm:true to proceed.`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
for (const suffix of ['', '-wal', '-shm'])
|
|
143
|
+
rmSync(dbFile + suffix, { force: true });
|
|
144
|
+
return { ...base, ok: true, detail: { dbFile, bytesFreed: bytesBefore } };
|
|
145
|
+
}
|
|
146
|
+
if (action === 'vacuum') {
|
|
147
|
+
const db = new Database(dbFile);
|
|
148
|
+
try {
|
|
149
|
+
db.pragma('wal_checkpoint(TRUNCATE)');
|
|
150
|
+
db.exec('VACUUM');
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
db.close();
|
|
154
|
+
}
|
|
155
|
+
const bytesAfter = sizeWithSidecars(dbFile);
|
|
156
|
+
return { ...base, ok: true, detail: { bytesBefore, bytesAfter, bytesReclaimed: Math.max(bytesBefore - bytesAfter, 0) } };
|
|
157
|
+
}
|
|
158
|
+
// clear-crawl-history: drop page_snapshots + audit_runs/findings beyond the KEEP_RECENT
|
|
159
|
+
// most recent, then VACUUM. Preview first so the refusal can name what goes.
|
|
160
|
+
const db = new Database(dbFile);
|
|
161
|
+
try {
|
|
162
|
+
const keepRuns = db.prepare(`SELECT run_id FROM audit_runs ORDER BY started_at DESC LIMIT ${KEEP_RECENT}`).all().map(r => r.run_id);
|
|
163
|
+
const keepCrawls = db.prepare(`SELECT crawl_id FROM (SELECT crawl_id, MAX(captured_at) t FROM page_snapshots GROUP BY crawl_id) ORDER BY t DESC LIMIT ${KEEP_RECENT}`).all().map(r => r.crawl_id);
|
|
164
|
+
const ph = (n) => Array.from({ length: n }, () => '?').join(',') || `''`;
|
|
165
|
+
const oldRuns = db.prepare(`SELECT COUNT(*) n FROM audit_runs WHERE run_id NOT IN (${ph(keepRuns.length)})`).get(...keepRuns).n;
|
|
166
|
+
const oldFindings = db.prepare(`SELECT COUNT(*) n FROM findings WHERE run_id NOT IN (${ph(keepRuns.length)})`).get(...keepRuns).n;
|
|
167
|
+
const oldSnapshots = db.prepare(`SELECT COUNT(*) n FROM page_snapshots WHERE crawl_id NOT IN (${ph(keepCrawls.length)})`).get(...keepCrawls).n;
|
|
168
|
+
if (!confirm) {
|
|
169
|
+
db.close();
|
|
170
|
+
return {
|
|
171
|
+
...base, ok: false, detail: { oldRuns, oldFindings, oldSnapshots, keepRecent: KEEP_RECENT },
|
|
172
|
+
refused: `REFUSED — clear-crawl-history is destructive and needs confirm:true. This would permanently delete ${oldRuns} audit runs, ${oldFindings} findings and ${oldSnapshots} page snapshots older than the ${KEEP_RECENT} most recent (change-detection history beyond that window is lost). Re-run with confirm:true to proceed.`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
db.transaction(() => {
|
|
176
|
+
db.prepare(`DELETE FROM findings WHERE run_id NOT IN (${ph(keepRuns.length)})`).run(...keepRuns);
|
|
177
|
+
db.prepare(`DELETE FROM audit_runs WHERE run_id NOT IN (${ph(keepRuns.length)})`).run(...keepRuns);
|
|
178
|
+
db.prepare(`DELETE FROM page_snapshots WHERE crawl_id NOT IN (${ph(keepCrawls.length)})`).run(...keepCrawls);
|
|
179
|
+
})();
|
|
180
|
+
db.pragma('wal_checkpoint(TRUNCATE)');
|
|
181
|
+
db.exec('VACUUM');
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
if (db.open)
|
|
185
|
+
db.close();
|
|
186
|
+
}
|
|
187
|
+
const bytesAfter = sizeWithSidecars(dbFile);
|
|
188
|
+
return {
|
|
189
|
+
...base, ok: true,
|
|
190
|
+
detail: { bytesBefore, bytesAfter, bytesReclaimed: Math.max(bytesBefore - bytesAfter, 0), keepRecent: KEEP_RECENT },
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=dataStorage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataStorage.js","sourceRoot":"","sources":["../../src/core/dataStorage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface DraftBrief {
|
|
2
|
+
url: string;
|
|
3
|
+
topQuery: string | null;
|
|
4
|
+
gap: string;
|
|
5
|
+
maxPassageScore: number | null;
|
|
6
|
+
voice: {
|
|
7
|
+
heading: string;
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
facts: {
|
|
11
|
+
heading: string;
|
|
12
|
+
text: string;
|
|
13
|
+
relevance: number;
|
|
14
|
+
}[];
|
|
15
|
+
brief: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Assemble a GROUNDED writing brief for a page so the host model (Claude) can draft the fix in the
|
|
19
|
+
* author's own voice — no bundled LLM. The page's existing chunks supply both the voice exemplars
|
|
20
|
+
* (match tone/pace/lexicon) and the facts (the reranker picks the most query-relevant ones), so the
|
|
21
|
+
* draft is phrased from the site's own material, never invented. urlKey omitted → the weakest
|
|
22
|
+
* (lowest max_passage_score) page.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildDraftBrief(dataDir: string, siteUrl: string, urlKey?: string): Promise<DraftBrief | {
|
|
25
|
+
error: string;
|
|
26
|
+
}>;
|
|
27
|
+
//# sourceMappingURL=draftBrief.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draftBrief.d.ts","sourceRoot":"","sources":["../../src/core/draftBrief.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3C,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9D,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAyDzC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { AuditDatabase } from './AuditDatabase.js';
|
|
2
|
+
import { dbPathFor } from './paths.js';
|
|
3
|
+
import { gscFreshness } from './gscFreshness.js';
|
|
4
|
+
import { scorePassages } from './reranker.js';
|
|
5
|
+
/**
|
|
6
|
+
* Assemble a GROUNDED writing brief for a page so the host model (Claude) can draft the fix in the
|
|
7
|
+
* author's own voice — no bundled LLM. The page's existing chunks supply both the voice exemplars
|
|
8
|
+
* (match tone/pace/lexicon) and the facts (the reranker picks the most query-relevant ones), so the
|
|
9
|
+
* draft is phrased from the site's own material, never invented. urlKey omitted → the weakest
|
|
10
|
+
* (lowest max_passage_score) page.
|
|
11
|
+
*/
|
|
12
|
+
export async function buildDraftBrief(dataDir, siteUrl, urlKey) {
|
|
13
|
+
const db = new AuditDatabase(dbPathFor(dataDir, siteUrl));
|
|
14
|
+
try {
|
|
15
|
+
const sel = 'SELECT url_key url, body_chunks bc, max_passage_score mps, max_passage_query mpq, title FROM pages';
|
|
16
|
+
const page = (urlKey
|
|
17
|
+
? db.db.prepare(`${sel} WHERE url_key = ?`).get(urlKey)
|
|
18
|
+
: db.db.prepare(`${sel} WHERE max_passage_score IS NOT NULL AND body_chunks IS NOT NULL ORDER BY max_passage_score ASC LIMIT 1`).get());
|
|
19
|
+
if (!page)
|
|
20
|
+
return { error: `No such page${urlKey ? ` ${urlKey}` : ''}.` };
|
|
21
|
+
if (!page.bc)
|
|
22
|
+
return { error: 'Page has no body_chunks — run start_crawl (then score_passages) first.' };
|
|
23
|
+
let chunks;
|
|
24
|
+
try {
|
|
25
|
+
chunks = JSON.parse(page.bc);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { error: 'Could not parse body_chunks.' };
|
|
29
|
+
}
|
|
30
|
+
chunks = chunks.filter(c => c.text && c.text.length > 40);
|
|
31
|
+
// Top query: the one the page was scored against; else the highest-impression GSC query.
|
|
32
|
+
let topQuery = page.mpq;
|
|
33
|
+
if (!topQuery) {
|
|
34
|
+
const maxDate = gscFreshness(db.db).effectiveMax;
|
|
35
|
+
if (maxDate) {
|
|
36
|
+
const q = db.db.prepare(`SELECT query FROM search_analytics WHERE page_key = ? AND query IS NOT NULL AND date <= '${maxDate}' AND date > date('${maxDate}','-28 days') GROUP BY query ORDER BY SUM(impressions) DESC LIMIT 1`).get(page.url);
|
|
37
|
+
topQuery = q?.query ?? null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Grounding facts — rerank the page's own chunks against the query, keep the most relevant.
|
|
41
|
+
let facts = [];
|
|
42
|
+
if (topQuery && chunks.length) {
|
|
43
|
+
const passages = chunks.map(c => `${c.heading ? `${c.heading}. ` : ''}${c.text}`);
|
|
44
|
+
const scores = await scorePassages(topQuery, passages);
|
|
45
|
+
facts = chunks.map((c, i) => ({ heading: c.heading ?? '', text: (c.text ?? '').slice(0, 500), relevance: Math.round((scores[i] ?? 0) * 100) / 100 }))
|
|
46
|
+
.sort((a, b) => b.relevance - a.relevance).slice(0, 4);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
facts = chunks.slice(0, 4).map(c => ({ heading: c.heading ?? '', text: (c.text ?? '').slice(0, 500), relevance: 0 }));
|
|
50
|
+
}
|
|
51
|
+
// Voice exemplars — a couple of the page's substantial passages (tone/pace/lexicon to match).
|
|
52
|
+
const voice = chunks.filter(c => (c.text ?? '').length > 120).slice(0, 2).map(c => ({ heading: c.heading ?? '', text: (c.text ?? '').slice(0, 450) }));
|
|
53
|
+
const gap = page.mps == null ? 'not scored (run score_passages)'
|
|
54
|
+
: page.mps < 3 ? `weak-passage (best passage scores ${page.mps} — no section confidently answers the query)`
|
|
55
|
+
: `the page already answers this (score ${page.mps}) — consider a refresh/expansion instead`;
|
|
56
|
+
const brief = [
|
|
57
|
+
`Make the SMALLEST change that closes the gap for the query “${topQuery ?? '(unknown)'}”: draft ONE self-contained passage to slot in (a clear heading + ~50–90 words) — not a rewrite. Preserve the rest of the page; this is a surgical addition (less intervention is better).`,
|
|
58
|
+
`Lead with a one-sentence direct answer, then support it.`,
|
|
59
|
+
`VOICE: match the tone, pace and lexicon of the voice samples exactly — first-person, from someone who owns and uses the gear, naming specific products. British English, contractions, no tidy "in conclusion" bow.`,
|
|
60
|
+
`AVOID AI slop: no em dashes; no clichés ("landscape", "elevate", "whether you're…", "in today's…", "dive in", "it's worth noting"); no formal-transition padding.`,
|
|
61
|
+
`GROUND STRICTLY: use only facts present in the facts/voice content below — invent no specs, prices, dates or claims. If a needed fact is missing, write around what's there and flag the gap rather than guessing.`,
|
|
62
|
+
].join(' ');
|
|
63
|
+
return { url: page.url, topQuery, gap, maxPassageScore: page.mps, voice, facts, brief };
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
db.close();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=draftBrief.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draftBrief.js","sourceRoot":"","sources":["../../src/core/draftBrief.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type UrlKeyOptions } from './url-key.js';
|
|
2
|
+
/**
|
|
3
|
+
* Internal = the seed host or its www/apex twin ONLY — not arbitrary sibling subdomains.
|
|
4
|
+
* accounts./shop./blog. etc. are separate properties; crawling (or following redirects into)
|
|
5
|
+
* them pulls in junk like Shopify's accounts.<domain> OAuth login pages. Shared by the
|
|
6
|
+
* link-classifier here and the crawler's redirect-target guard.
|
|
7
|
+
*/
|
|
8
|
+
export declare const isInternalHost: (targetHost: string, baseHost: string) => boolean;
|
|
9
|
+
export interface ExtractedLink {
|
|
10
|
+
targetUrl: string;
|
|
11
|
+
targetKey: string;
|
|
12
|
+
anchor: string;
|
|
13
|
+
placement: 'navigation' | 'footer' | 'body';
|
|
14
|
+
rel: string;
|
|
15
|
+
isInternal: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface ExtractedPage {
|
|
18
|
+
title: string | null;
|
|
19
|
+
titleLength: number;
|
|
20
|
+
metaDescription: string | null;
|
|
21
|
+
metaDescriptionLength: number;
|
|
22
|
+
h1: string | null;
|
|
23
|
+
h1Count: number;
|
|
24
|
+
wordCount: number;
|
|
25
|
+
lang: string | null;
|
|
26
|
+
charset: string | null;
|
|
27
|
+
canonicalUrl: string | null;
|
|
28
|
+
canonicalKey: string | null;
|
|
29
|
+
robots: string | null;
|
|
30
|
+
viewport: string | null;
|
|
31
|
+
noindex: boolean;
|
|
32
|
+
jsonLd: string | null;
|
|
33
|
+
ogTags: string | null;
|
|
34
|
+
hreflang: string | null;
|
|
35
|
+
internalLinks: number;
|
|
36
|
+
externalLinks: number;
|
|
37
|
+
imageCount: number;
|
|
38
|
+
imagesWithoutAlt: number;
|
|
39
|
+
imagesMissingDimensions: number;
|
|
40
|
+
canonicalCount: number;
|
|
41
|
+
canonicalRelative: boolean;
|
|
42
|
+
h2Count: number;
|
|
43
|
+
headingSkips: number;
|
|
44
|
+
relNext: boolean;
|
|
45
|
+
relPrev: boolean;
|
|
46
|
+
relAmphtml: string | null;
|
|
47
|
+
mixedContentCount: number;
|
|
48
|
+
twitterTags: string | null;
|
|
49
|
+
hasMicrodata: boolean;
|
|
50
|
+
hasRdfa: boolean;
|
|
51
|
+
hasFavicon: boolean;
|
|
52
|
+
hasAnalytics: boolean;
|
|
53
|
+
bodyChunks: {
|
|
54
|
+
heading: string;
|
|
55
|
+
level: number;
|
|
56
|
+
text: string;
|
|
57
|
+
}[];
|
|
58
|
+
links: ExtractedLink[];
|
|
59
|
+
imageSrcs: string[];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Extract the SEO-relevant surface from raw HTML. Pure function of bytes (D).
|
|
63
|
+
* `baseHost` is the seed apex host for internal/external classification;
|
|
64
|
+
* `keyOpts` is the url-key normalisation config for this property.
|
|
65
|
+
*/
|
|
66
|
+
export declare function extractPage(html: string, pageUrl: string, baseHost: string, keyOpts: UrlKeyOptions, xRobotsTag?: string | null): ExtractedPage;
|
|
67
|
+
//# sourceMappingURL=extract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/core/extract.ts"],"names":[],"mappings":"AACA,OAAO,EAAU,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAE1D;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,YAAY,MAAM,EAAE,UAAU,MAAM,KAAG,OAKrE,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,YAAY,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,uBAAuB,EAAE,MAAM,CAAC;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC/D,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AASD;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,EACtB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,GACzB,aAAa,CA2Nf"}
|