aeoptimize 0.6.2 → 0.7.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +13 -0
- package/CONTRIBUTING.md +6 -0
- package/README.md +103 -6
- package/dist/cli/index.js +162 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/core/audit.d.ts +15 -0
- package/dist/core/audit.js +301 -0
- package/dist/core/audit.js.map +1 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +3 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/scanner.d.ts +22 -0
- package/dist/core/scanner.js +108 -28
- package/dist/core/scanner.js.map +1 -1
- package/dist/core/site-audit.d.ts +21 -0
- package/dist/core/site-audit.js +605 -0
- package/dist/core/site-audit.js.map +1 -0
- package/dist/core/static-audit.d.ts +30 -0
- package/dist/core/static-audit.js +202 -0
- package/dist/core/static-audit.js.map +1 -0
- package/dist/core/types.d.ts +84 -0
- package/docs/methodology.md +21 -0
- package/docs/release-v0.7.md +42 -0
- package/examples/github-action-sample/.github/workflows/aeoptimize.yml +1 -1
- package/examples/github-action-sample/README.md +1 -1
- package/package.json +2 -1
- package/scripts/verify-release-candidate.sh +17 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type StaticAuditStatus = 'PASS' | 'WARNING' | 'FAIL' | 'N/A';
|
|
2
|
+
export interface StaticAuditOptions {
|
|
3
|
+
expectIndexable?: boolean;
|
|
4
|
+
/** Absolute deployed page URL, or deployment root when auditing a directory. */
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface StaticAuditCheck {
|
|
8
|
+
id: string;
|
|
9
|
+
status: StaticAuditStatus;
|
|
10
|
+
evidence: string[];
|
|
11
|
+
message: string;
|
|
12
|
+
remediation: string | null;
|
|
13
|
+
validation: string;
|
|
14
|
+
}
|
|
15
|
+
export interface StaticPageAudit {
|
|
16
|
+
target: string;
|
|
17
|
+
checks: StaticAuditCheck[];
|
|
18
|
+
}
|
|
19
|
+
export interface StaticAuditReport {
|
|
20
|
+
contractVersion: '1.0';
|
|
21
|
+
source: 'local-html';
|
|
22
|
+
pages: StaticPageAudit[];
|
|
23
|
+
summary: Record<StaticAuditStatus, number>;
|
|
24
|
+
limitations: string[];
|
|
25
|
+
timestamp: string;
|
|
26
|
+
}
|
|
27
|
+
/** Audits observed source HTML only; it neither fetches URLs nor changes the readiness score. */
|
|
28
|
+
export declare function auditHtml(html: string, target: string, options?: StaticAuditOptions): StaticPageAudit;
|
|
29
|
+
/** Recursively audits local HTML files. Read/parse failures stop the run rather than silently omitting pages. */
|
|
30
|
+
export declare function auditPath(input: string, options?: StaticAuditOptions): Promise<StaticAuditReport>;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import * as cheerio from 'cheerio';
|
|
2
|
+
import { lstat, readFile, readdir } from 'node:fs/promises';
|
|
3
|
+
import { extname, join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
function httpUrl(value, base) {
|
|
5
|
+
if (/[\u0000-\u0020\u007f]/.test(value))
|
|
6
|
+
throw new Error('URL contains whitespace or control characters');
|
|
7
|
+
const url = new URL(value, base);
|
|
8
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) {
|
|
9
|
+
throw new Error('Expected an HTTP(S) URL without credentials');
|
|
10
|
+
}
|
|
11
|
+
return url;
|
|
12
|
+
}
|
|
13
|
+
/** Audits observed source HTML only; it neither fetches URLs nor changes the readiness score. */
|
|
14
|
+
export function auditHtml(html, target, options = {}) {
|
|
15
|
+
const $ = cheerio.load(html);
|
|
16
|
+
const checks = [];
|
|
17
|
+
const add = (id, status, evidence, message, remediation, validation) => {
|
|
18
|
+
checks.push({ id, status, evidence, message, remediation, validation });
|
|
19
|
+
};
|
|
20
|
+
const pageUrl = options.baseUrl ? httpUrl(options.baseUrl).href : undefined;
|
|
21
|
+
const titles = $('title').map((_, el) => $(el).text().trim()).get();
|
|
22
|
+
const titleOk = titles.length === 1 && titles[0].length > 0;
|
|
23
|
+
add('document-title', titleOk ? 'PASS' : 'WARNING', titles, titleOk ? 'One non-empty title element found.' : 'Expected one non-empty title element.', titleOk ? null : 'Write one descriptive title in the document head; remove duplicate title elements.', 'Rebuild and inspect the title element in the generated HTML.');
|
|
24
|
+
const descriptions = $('meta').filter((_, el) => ($(el).attr('name') || '').trim().toLowerCase() === 'description')
|
|
25
|
+
.map((_, el) => $(el).attr('content')?.trim() || '').get();
|
|
26
|
+
const descriptionOk = descriptions.length === 1 && descriptions[0].length > 0;
|
|
27
|
+
add('meta-description', descriptionOk ? 'PASS' : 'WARNING', descriptions, descriptionOk ? 'One non-empty meta description found.' : 'Expected one non-empty meta description.', descriptionOk ? null : 'Write a page-specific description and remove duplicate description tags.', 'Inspect the rebuilt meta description; search engines may select a different snippet.');
|
|
28
|
+
const canonicals = $('link').filter((_, el) => ($(el).attr('rel') || '').toLowerCase().split(/\s+/).includes('canonical'))
|
|
29
|
+
.map((_, el) => $(el).attr('href')?.trim() || '').get();
|
|
30
|
+
const canonicalValidation = 'Rebuild and check the canonical URL, then verify its HTTP response and indexability on the deployed site.';
|
|
31
|
+
if (canonicals.length === 0) {
|
|
32
|
+
add('canonical', 'WARNING', [], 'No HTML canonical declaration found; HTTP canonical headers were not checked.', 'Review the intended canonical URL and declare it in HTML or the HTTP Link header where appropriate.', canonicalValidation);
|
|
33
|
+
}
|
|
34
|
+
else if (canonicals.length > 1 || !canonicals[0]) {
|
|
35
|
+
add('canonical', 'FAIL', canonicals, 'Canonical declarations are multiple or empty.', 'Emit one non-empty canonical declaration for the intended page.', canonicalValidation);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const href = canonicals[0];
|
|
39
|
+
let base = pageUrl;
|
|
40
|
+
const htmlBase = $('base[href]').first().attr('href');
|
|
41
|
+
try {
|
|
42
|
+
const isAbsolute = /^[a-z][a-z\d+.-]*:/i.test(href);
|
|
43
|
+
if (htmlBase !== undefined && !isAbsolute) {
|
|
44
|
+
const baseValue = htmlBase.trim();
|
|
45
|
+
if (!pageUrl && !/^[a-z][a-z\d+.-]*:/i.test(baseValue)) {
|
|
46
|
+
httpUrl(baseValue, 'https://audit.invalid/');
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
base = httpUrl(baseValue, pageUrl).href;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!isAbsolute && !base) {
|
|
53
|
+
// Validate the reference shape without inventing the deployed origin.
|
|
54
|
+
httpUrl(href, 'https://audit.invalid/');
|
|
55
|
+
add('canonical', 'N/A', [href], 'A relative canonical needs the deployed page URL for resolution.', 'Supply --base-url or emit an absolute canonical URL.', canonicalValidation);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const url = httpUrl(href, base);
|
|
59
|
+
const hadFragment = Boolean(url.hash);
|
|
60
|
+
url.hash = '';
|
|
61
|
+
add('canonical', hadFragment ? 'WARNING' : 'PASS', [url.href], hadFragment ? 'Canonical contains a fragment.' : 'One HTTP(S) canonical URL resolves; its destination was not fetched.', hadFragment ? 'Use a canonical URL without a fragment.' : null, canonicalValidation);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
add('canonical', 'FAIL', canonicals, 'Canonical URL or HTML base cannot resolve to an HTTP(S) URL without credentials.', 'Correct the canonical href and any HTML base href.', canonicalValidation);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const robotTags = $('meta').filter((_, el) => ['robots', 'googlebot'].includes(($(el).attr('name') || '').trim().toLowerCase()));
|
|
69
|
+
const robotEvidence = [];
|
|
70
|
+
let blocksIndexing = false;
|
|
71
|
+
robotTags.each((_, el) => {
|
|
72
|
+
const name = ($(el).attr('name') || '').trim().toLowerCase();
|
|
73
|
+
const content = $(el).attr('content') || '';
|
|
74
|
+
robotEvidence.push(`${name}: ${content}`);
|
|
75
|
+
// Keep parameter values attached to their directive, e.g. max-image-preview: none.
|
|
76
|
+
const tokens = content.toLowerCase().replace(/:\s+/g, ':').split(/[\s,]+/);
|
|
77
|
+
if (tokens.includes('noindex') || tokens.includes('none'))
|
|
78
|
+
blocksIndexing = true;
|
|
79
|
+
});
|
|
80
|
+
add('indexing-directives', blocksIndexing ? (options.expectIndexable ? 'FAIL' : 'WARNING') : 'PASS', robotEvidence, blocksIndexing
|
|
81
|
+
? 'An HTML robots or googlebot directive blocks standalone indexing; index does not override noindex.'
|
|
82
|
+
: 'No noindex or none token found in HTML robots/googlebot meta tags. HTTP headers and actual indexing remain unverified.', blocksIndexing ? 'Confirm the page policy. Remove noindex/none only if this page should be indexed.' : null, 'Inspect every robots/googlebot tag after rebuilding, then check deployed headers and Search Console URL Inspection.');
|
|
83
|
+
const blocks = $('script').filter((_, el) => ($(el).attr('type') || '').trim().toLowerCase() === 'application/ld+json');
|
|
84
|
+
const jsonEvidence = [];
|
|
85
|
+
let invalidJson = false;
|
|
86
|
+
const isObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
87
|
+
blocks.each((index, el) => {
|
|
88
|
+
try {
|
|
89
|
+
const value = JSON.parse($(el).html() || '');
|
|
90
|
+
if (!(isObject(value) || (Array.isArray(value) && value.every(isObject)))) {
|
|
91
|
+
invalidJson = true;
|
|
92
|
+
jsonEvidence.push(`Block ${index + 1}: expected an object or an array of objects.`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
jsonEvidence.push(`Block ${index + 1}: JSON parses with an object or object-array root.`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
invalidJson = true;
|
|
100
|
+
jsonEvidence.push(`Block ${index + 1}: malformed JSON.`);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
add('jsonld-syntax', blocks.length === 0 ? 'N/A' : invalidJson ? 'FAIL' : 'PASS', jsonEvidence, blocks.length === 0 ? 'No JSON-LD present; structured data is optional.'
|
|
104
|
+
: invalidJson ? 'JSON-LD has a syntax or root-shape error.'
|
|
105
|
+
: 'JSON syntax and root shape pass. Schema vocabulary, visible-content consistency and rich-result eligibility were not validated.', invalidJson ? 'Fix the reported blocks and validate the appropriate schema against visible page content.' : null, 'Re-run this audit, then use Schema Markup Validator and the applicable search-engine validation tool.');
|
|
106
|
+
add('http-indexing', 'N/A', [], 'Local HTML cannot establish HTTP status, X-Robots-Tag, robots.txt access, redirects, or actual search indexing.', 'Check the deployed response and crawler policy; use Search Console for Google indexing evidence.', 'Inspect the deployed HTTP response and Search Console URL Inspection.');
|
|
107
|
+
return { target, checks };
|
|
108
|
+
}
|
|
109
|
+
function markDuplicates(pages, sourceId, id, normalize) {
|
|
110
|
+
const groups = new Map();
|
|
111
|
+
for (const page of pages) {
|
|
112
|
+
const check = page.checks.find((item) => item.id === sourceId);
|
|
113
|
+
if (!check || (check.status !== 'PASS' && !(sourceId === 'canonical' && check.status === 'WARNING'))
|
|
114
|
+
|| check.evidence.length !== 1)
|
|
115
|
+
continue;
|
|
116
|
+
const value = normalize(check.evidence[0]);
|
|
117
|
+
if (value)
|
|
118
|
+
groups.set(value, [...(groups.get(value) || []), page]);
|
|
119
|
+
}
|
|
120
|
+
for (const [value, matches] of groups) {
|
|
121
|
+
if (matches.length < 2)
|
|
122
|
+
continue;
|
|
123
|
+
for (const page of matches) {
|
|
124
|
+
page.checks.push({
|
|
125
|
+
id, status: 'WARNING', evidence: [value, ...matches.map((match) => match.target)],
|
|
126
|
+
message: sourceId === 'canonical'
|
|
127
|
+
? 'Multiple audited files point to the same canonical. This may be an intentional consolidation.'
|
|
128
|
+
: 'Multiple audited files share a title. Review whether they serve distinct page purposes.',
|
|
129
|
+
remediation: 'Review the listed files and correct template reuse only where the duplication is unintended.',
|
|
130
|
+
validation: 'Rebuild and rerun the directory audit; confirm any intentional duplicates manually.',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Recursively audits local HTML files. Read/parse failures stop the run rather than silently omitting pages. */
|
|
136
|
+
export async function auditPath(input, options = {}) {
|
|
137
|
+
if (options.baseUrl)
|
|
138
|
+
httpUrl(options.baseUrl);
|
|
139
|
+
const root = resolve(input);
|
|
140
|
+
const rootStat = await lstat(root);
|
|
141
|
+
if (rootStat.isSymbolicLink())
|
|
142
|
+
throw new Error('Symbolic link targets are not supported.');
|
|
143
|
+
const files = [];
|
|
144
|
+
async function collect(path) {
|
|
145
|
+
const info = await lstat(path);
|
|
146
|
+
if (info.isSymbolicLink())
|
|
147
|
+
return;
|
|
148
|
+
if (info.isDirectory()) {
|
|
149
|
+
const entries = await readdir(path);
|
|
150
|
+
for (const entry of entries.sort()) {
|
|
151
|
+
if (entry.startsWith('.') || entry === 'node_modules')
|
|
152
|
+
continue;
|
|
153
|
+
await collect(join(path, entry));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else if (info.isFile() && ['.html', '.htm'].includes(extname(path).toLowerCase())) {
|
|
157
|
+
files.push(path);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (!rootStat.isDirectory() && !['.html', '.htm'].includes(extname(root).toLowerCase())) {
|
|
161
|
+
throw new Error('Static audit supports .html and .htm files or directories containing HTML.');
|
|
162
|
+
}
|
|
163
|
+
await collect(root);
|
|
164
|
+
if (!files.length)
|
|
165
|
+
throw new Error('No HTML files found to audit.');
|
|
166
|
+
const pages = [];
|
|
167
|
+
for (const file of files.sort()) {
|
|
168
|
+
const info = await lstat(file);
|
|
169
|
+
if (info.isSymbolicLink() || !info.isFile())
|
|
170
|
+
throw new Error(`Input changed during audit: ${file}`);
|
|
171
|
+
if (info.size > 5 * 1024 * 1024)
|
|
172
|
+
throw new Error(`HTML file exceeds the 5 MB audit limit: ${file}`);
|
|
173
|
+
let baseUrl = options.baseUrl;
|
|
174
|
+
if (rootStat.isDirectory() && baseUrl) {
|
|
175
|
+
const rootUrl = httpUrl(baseUrl);
|
|
176
|
+
if (rootUrl.search || rootUrl.hash)
|
|
177
|
+
throw new Error('A directory --base-url must not contain a query or fragment.');
|
|
178
|
+
if (!rootUrl.pathname.endsWith('/'))
|
|
179
|
+
rootUrl.pathname += '/';
|
|
180
|
+
const deployedPath = relative(root, file).split(sep).map(encodeURIComponent).join('/');
|
|
181
|
+
baseUrl = new URL(deployedPath, rootUrl).href;
|
|
182
|
+
}
|
|
183
|
+
pages.push(auditHtml(await readFile(file, 'utf8'), file, { ...options, baseUrl }));
|
|
184
|
+
}
|
|
185
|
+
markDuplicates(pages, 'document-title', 'duplicate-document-title', (value) => value.replace(/\s+/g, ' ').trim().toLowerCase());
|
|
186
|
+
markDuplicates(pages, 'canonical', 'duplicate-canonical', (value) => value);
|
|
187
|
+
const summary = { PASS: 0, WARNING: 0, FAIL: 0, 'N/A': 0 };
|
|
188
|
+
for (const page of pages)
|
|
189
|
+
for (const check of page.checks)
|
|
190
|
+
summary[check.status]++;
|
|
191
|
+
return {
|
|
192
|
+
contractVersion: '1.0', source: 'local-html', pages, summary,
|
|
193
|
+
limitations: [
|
|
194
|
+
'Source HTML only. No browser rendering, network requests, HTTP headers, robots.txt, sitemap or search-performance data were inspected.',
|
|
195
|
+
'PASS applies to the named check only; it does not establish crawlability, indexing, ranking, AI citation or rich-result eligibility.',
|
|
196
|
+
'Cross-page findings cover only audited files. Symbolic links, hidden entries and node_modules are excluded.',
|
|
197
|
+
'Directory base URLs map relative file paths directly; hosting-specific clean-URL rewrites are not inferred.',
|
|
198
|
+
],
|
|
199
|
+
timestamp: new Date().toISOString(),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=static-audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static-audit.js","sourceRoot":"","sources":["../../src/core/static-audit.ts"],"names":[],"mappings":"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"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -35,6 +35,13 @@ export interface Link {
|
|
|
35
35
|
text: string;
|
|
36
36
|
rel?: string;
|
|
37
37
|
}
|
|
38
|
+
export interface ImageReference {
|
|
39
|
+
src: string;
|
|
40
|
+
alt?: string;
|
|
41
|
+
width?: string;
|
|
42
|
+
height?: string;
|
|
43
|
+
loading?: string;
|
|
44
|
+
}
|
|
38
45
|
export interface JsonLdObject {
|
|
39
46
|
'@type'?: string;
|
|
40
47
|
'@context'?: string;
|
|
@@ -43,16 +50,93 @@ export interface JsonLdObject {
|
|
|
43
50
|
export interface ParsedDocument {
|
|
44
51
|
url: string;
|
|
45
52
|
title: string;
|
|
53
|
+
documentTitle?: string;
|
|
46
54
|
html?: string;
|
|
47
55
|
markdown?: string;
|
|
48
56
|
frontmatter?: Record<string, unknown>;
|
|
49
57
|
headings: Heading[];
|
|
50
58
|
paragraphs: string[];
|
|
51
59
|
jsonLd: JsonLdObject[];
|
|
60
|
+
jsonLdBlockCount?: number;
|
|
61
|
+
jsonLdErrors?: string[];
|
|
52
62
|
metaTags: Record<string, string>;
|
|
63
|
+
metaTagValues?: Record<string, string[]>;
|
|
53
64
|
links: Link[];
|
|
65
|
+
images?: ImageReference[];
|
|
66
|
+
language?: string;
|
|
67
|
+
canonicalLinks?: string[];
|
|
54
68
|
rawText: string;
|
|
55
69
|
}
|
|
70
|
+
export type AuditStatus = 'PASS' | 'WARNING' | 'FAIL' | 'N/A';
|
|
71
|
+
export type AuditEvidenceSource = 'http' | 'html' | 'markdown' | 'derived';
|
|
72
|
+
export type AuditEvidenceValue = string | number | boolean | null | string[];
|
|
73
|
+
export interface AuditEvidence {
|
|
74
|
+
source: AuditEvidenceSource;
|
|
75
|
+
observed: Record<string, AuditEvidenceValue>;
|
|
76
|
+
}
|
|
77
|
+
export interface AuditCheck {
|
|
78
|
+
id: string;
|
|
79
|
+
label: string;
|
|
80
|
+
status: AuditStatus;
|
|
81
|
+
evidence: AuditEvidence[];
|
|
82
|
+
explanation: string;
|
|
83
|
+
remediation: string | null;
|
|
84
|
+
validation: string;
|
|
85
|
+
}
|
|
86
|
+
export interface AuditReport {
|
|
87
|
+
contractVersion: '1.0';
|
|
88
|
+
target: {
|
|
89
|
+
type: 'url' | 'file';
|
|
90
|
+
input: string;
|
|
91
|
+
finalUrl?: string;
|
|
92
|
+
};
|
|
93
|
+
rendering: 'response-html' | 'browser' | 'local-html' | 'markdown';
|
|
94
|
+
checks: AuditCheck[];
|
|
95
|
+
summary: Record<AuditStatus, number>;
|
|
96
|
+
limitations: string[];
|
|
97
|
+
timestamp: string;
|
|
98
|
+
}
|
|
99
|
+
export interface SiteAuditPage {
|
|
100
|
+
requestedUrl: string;
|
|
101
|
+
finalUrl: string;
|
|
102
|
+
status: number;
|
|
103
|
+
statusText: string;
|
|
104
|
+
redirects: string[];
|
|
105
|
+
contentType: string | null;
|
|
106
|
+
title: string | null;
|
|
107
|
+
language: string | null;
|
|
108
|
+
canonicalLinks: string[];
|
|
109
|
+
internalLinkCount: number;
|
|
110
|
+
externalLinkCount: number;
|
|
111
|
+
discoveredFrom: string[];
|
|
112
|
+
}
|
|
113
|
+
export interface SiteAuditSitemap {
|
|
114
|
+
url: string;
|
|
115
|
+
status: number;
|
|
116
|
+
kind: 'urlset' | 'index' | 'unknown';
|
|
117
|
+
urlCount: number;
|
|
118
|
+
}
|
|
119
|
+
export interface SiteAuditReport {
|
|
120
|
+
contractVersion: '1.0';
|
|
121
|
+
startUrl: string;
|
|
122
|
+
origin: string;
|
|
123
|
+
maxPages: number;
|
|
124
|
+
crawledPages: number;
|
|
125
|
+
truncated: boolean;
|
|
126
|
+
robots: {
|
|
127
|
+
url: string;
|
|
128
|
+
status: number;
|
|
129
|
+
applicableRules: string[];
|
|
130
|
+
sitemapUrls: string[];
|
|
131
|
+
skippedUrls: string[];
|
|
132
|
+
};
|
|
133
|
+
sitemaps: SiteAuditSitemap[];
|
|
134
|
+
pages: SiteAuditPage[];
|
|
135
|
+
checks: AuditCheck[];
|
|
136
|
+
summary: Record<AuditStatus, number>;
|
|
137
|
+
limitations: string[];
|
|
138
|
+
timestamp: string;
|
|
139
|
+
}
|
|
56
140
|
export interface RuleResult {
|
|
57
141
|
score: number;
|
|
58
142
|
maxScore: number;
|
package/docs/methodology.md
CHANGED
|
@@ -66,6 +66,27 @@ A scoring-rule change must include:
|
|
|
66
66
|
|
|
67
67
|
The v0.6 fixture corpus is published in [`fixtures/v0.6/rule-corpus.ts`](../fixtures/v0.6/rule-corpus.ts) and is enforced by the release-contract tests. Outcome research, if added later, will be reported separately from the readiness score.
|
|
68
68
|
|
|
69
|
+
## Evidence-backed audit contract
|
|
70
|
+
|
|
71
|
+
The `audit` command is separate from the v0.6 score contract. It reports one of four statuses for each bounded check:
|
|
72
|
+
|
|
73
|
+
| Status | Meaning |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| `PASS` | The inspected evidence satisfies the check's documented condition. This is not an external-outcome guarantee. |
|
|
76
|
+
| `WARNING` | A deterministic observation needs contextual review or may be intentional. |
|
|
77
|
+
| `FAIL` | The inspected evidence confirms a structural or retrieval failure within the check's scope. |
|
|
78
|
+
| `N/A` | The required evidence is unavailable or the check does not apply to this source type. |
|
|
79
|
+
|
|
80
|
+
Each check includes observed evidence, an explanation, optional remediation, and a validation step. The contract does not derive robots.txt policy, sitemap membership, hreflang reciprocity, site-wide duplication, orphan status, Core Web Vitals, analytics, or search-engine index state from a single document.
|
|
81
|
+
|
|
82
|
+
### Bounded site audit
|
|
83
|
+
|
|
84
|
+
The `audit-site` contract uses a same-origin, sequential queue with an explicit 1–200 page limit. It processes links in discovery order before sitemap-only seeds, reads applicable `aeoptimize` or wildcard robots rules, and does not follow subsequent redirects outside the audited origin.
|
|
85
|
+
|
|
86
|
+
The crawler uses response HTML without browser rendering. A Sitemap-only URL with no internal inlink is reported as an orphan candidate because JavaScript navigation, pages beyond the configured limit, and other discovery sources may still link to it. Broken-link findings require an observed failed response; queued but unfetched links remain warnings.
|
|
87
|
+
|
|
88
|
+
robots.txt matching covers applicable user-agent groups, `Allow`, `Disallow`, `*` wildcards, and `$` endings. Unusual policies need manual review. An unavailable robots.txt response that is not an ordinary 404 causes conservative skipping of subsequent matching URLs.
|
|
89
|
+
|
|
69
90
|
## Primary sources
|
|
70
91
|
|
|
71
92
|
- [Google Search: AI features and your website](https://developers.google.com/search/docs/appearance/ai-features)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# v0.7 release and rollback guide
|
|
2
|
+
|
|
3
|
+
Version 0.7.0 adds three evidence-backed audit workflows while retaining the v0.6.0 scoring methodology and existing `scan --json` contract.
|
|
4
|
+
|
|
5
|
+
- `audit` inspects one URL or a local HTML/Markdown file.
|
|
6
|
+
- `audit-build` checks built HTML with cross-page review and optional CI failure gates.
|
|
7
|
+
- `audit-site` performs a bounded, same-origin crawl and compares discovered links, canonical declarations and sitemap entries.
|
|
8
|
+
|
|
9
|
+
Audit statuses describe the evidence collected. Unavailable evidence remains unassessed. Actual search indexing, rankings, traffic and AI citation outcomes are outside these contracts.
|
|
10
|
+
|
|
11
|
+
## Release acceptance
|
|
12
|
+
|
|
13
|
+
1. Build from a clean release commit after `npm ci` and run `npm run release:check`.
|
|
14
|
+
2. Require passing Node.js 22 and 24 CI, package reproducibility, and Action contract checks on the exact merged `main` commit.
|
|
15
|
+
3. Preserve the candidate manifest and SHA-256. Confirm all three CLI aliases and audit commands work from that exact packed artifact in a clean consumer.
|
|
16
|
+
4. Verify the npm account immediately before publication, fetch `origin/main`, and require the publish source gate to confirm the exact commit.
|
|
17
|
+
5. Publish npm 0.7.0, then create the immutable `v0.7.0` tag and GitHub Release at that same commit. These external steps require maintainer authorization.
|
|
18
|
+
|
|
19
|
+
`prepublishOnly` runs the candidate and source gates. The source gate requires `HEAD` to equal the freshly fetched `origin/main` commit. Do not bypass it or publish a pre-merge feature commit.
|
|
20
|
+
|
|
21
|
+
## Publication readback
|
|
22
|
+
|
|
23
|
+
Verify npm metadata and the exact version, download its tarball, compare its SHA-256 with the candidate, and install all CLI aliases from those verified bytes. Confirm the immutable tag and published GitHub Release independently.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm view aeoptimize version dist-tags --json
|
|
27
|
+
npm view aeoptimize@0.7.0 version gitHead dist --json
|
|
28
|
+
bash scripts/verify-release-v0.6.sh <verified-release-commit> <verified-package-sha256>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The public verifier keeps its historical filename and reads the expected version from package.json. When npm supplies `gitHead`, it must match the release commit. The tarball hash is required even when that optional metadata field is absent.
|
|
32
|
+
|
|
33
|
+
## Rollback
|
|
34
|
+
|
|
35
|
+
With explicit rollback authorization, restore `latest` to the last fully published release and deprecate the problematic version.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm dist-tag add aeoptimize@0.6.2 latest
|
|
39
|
+
npm deprecate aeoptimize@0.7.0 "Use 0.6.2 while an audit regression is corrected."
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Update the GitHub Release with the same warning. Preserve existing tags and exact-version artifacts, then fix forward with a new version and repeat the release gates.
|
|
@@ -6,4 +6,4 @@ This directory is a copyable end-to-end sample for the v0.6 Action contract.
|
|
|
6
6
|
- `site/index.html` is a deterministic public input.
|
|
7
7
|
- The Action is advisory by default. The sample does not block a pull request on an unreviewed score threshold.
|
|
8
8
|
|
|
9
|
-
The workflow becomes reproducible only after both `aeoptimize@0.
|
|
9
|
+
The workflow becomes reproducible only after both `aeoptimize@0.7.0` exists on npm and the immutable `v0.7.0` Git tag points to the matching release commit. Until both artifacts exist, use the local CLI or the release-candidate package during controlled verification.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aeoptimize",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Deterministic content-readiness lint for static websites and documentation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/core/index.js",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
".claude-plugin/",
|
|
27
27
|
"docs/methodology.md",
|
|
28
28
|
"docs/release-v0.6.md",
|
|
29
|
+
"docs/release-v0.7.md",
|
|
29
30
|
"CHANGELOG.md",
|
|
30
31
|
"CONTRIBUTING.md",
|
|
31
32
|
"ROADMAP.md",
|
|
@@ -52,6 +52,10 @@ PACKAGE_SHA256=$(node -e "const crypto=require('node:crypto');const fs=require('
|
|
|
52
52
|
|
|
53
53
|
jq -e '
|
|
54
54
|
(.[0].files | map(.path) | index("dist/cli/index.js")) != null and
|
|
55
|
+
(.[0].files | map(.path) | index("dist/core/audit.js")) != null and
|
|
56
|
+
(.[0].files | map(.path) | index("dist/core/static-audit.js")) != null and
|
|
57
|
+
(.[0].files | map(.path) | index("dist/core/site-audit.js")) != null and
|
|
58
|
+
(.[0].files | map(.path) | index("docs/release-v0.7.md")) != null and
|
|
55
59
|
(.[0].files | map(.path) | index("fixtures/v0.6/rule-corpus.ts")) != null and
|
|
56
60
|
(.[0].files | map(.path) | index("examples/github-action-sample/.github/workflows/aeoptimize.yml")) != null and
|
|
57
61
|
(.[0].files | map(.path) | index("scripts/verify-release-candidate.sh")) != null and
|
|
@@ -70,6 +74,19 @@ for binary in aeoptimize aeo aeo-cli; do
|
|
|
70
74
|
fi
|
|
71
75
|
done
|
|
72
76
|
|
|
77
|
+
for audit_command in audit audit-build audit-site; do
|
|
78
|
+
"$CONSUMER_ROOT/node_modules/.bin/aeoptimize" "$audit_command" --help >/dev/null
|
|
79
|
+
done
|
|
80
|
+
|
|
81
|
+
"$CONSUMER_ROOT/node_modules/.bin/aeoptimize" audit \
|
|
82
|
+
"$CONSUMER_ROOT/node_modules/aeoptimize/examples/github-action-sample/site/index.html" \
|
|
83
|
+
--json > "$VERIFY_ROOT/page-audit.json"
|
|
84
|
+
"$CONSUMER_ROOT/node_modules/.bin/aeoptimize" audit-build \
|
|
85
|
+
"$CONSUMER_ROOT/node_modules/aeoptimize/examples/github-action-sample/site" \
|
|
86
|
+
--json > "$VERIFY_ROOT/build-audit.json"
|
|
87
|
+
jq -e '.contractVersion == "1.0" and (.checks | length > 0)' "$VERIFY_ROOT/page-audit.json" >/dev/null
|
|
88
|
+
jq -e '.contractVersion == "1.0" and .source == "local-html" and (.pages | length == 1)' "$VERIFY_ROOT/build-audit.json" >/dev/null
|
|
89
|
+
|
|
73
90
|
MANIFEST=$(jq -n \
|
|
74
91
|
--arg version "$PACKAGE_VERSION" \
|
|
75
92
|
--arg filename "$PACKAGE_FILENAME" \
|