create-ailk 0.10.0 → 0.11.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/README.md +7 -2
- package/dist/cli.js +5 -1
- package/dist/content-payloads.d.ts +37 -0
- package/dist/content-payloads.js +64 -0
- package/dist/lib/fetch-module.d.ts +3 -3
- package/dist/lib/fetch-module.js +11 -5
- package/dist/lib/fetch-template.d.ts +1 -1
- package/dist/lib/fetch-template.js +7 -4
- package/dist/module-architecture.d.ts +9 -0
- package/dist/module-architecture.js +43 -12
- package/dist/parse-args.d.ts +2 -0
- package/dist/parse-args.js +7 -0
- package/dist/surfaces.js +2 -3
- package/package.json +3 -3
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +4 -3
- package/templates/.claude/settings.json +12 -0
- package/templates/.env.example +1 -1
- package/templates/CLAUDE.md +38 -22
- package/templates/README.md +12 -12
- package/templates/apps/api/jest.config.cjs +1 -0
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/mcp/CLAUDE.md +23 -10
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +160 -17
- package/templates/apps/mcp/__tests__/index.test.ts +118 -1
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +425 -5
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +67 -3
- package/templates/apps/mcp/__tests__/server.test.ts +162 -1
- package/templates/apps/mcp/src/index.ts +35 -3
- package/templates/apps/mcp/src/lib/commerce-claim.ts +193 -24
- package/templates/apps/mcp/src/server.ts +77 -5
- package/templates/apps/mcp/src/tools/purchase.ts +1 -1
- package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +15 -0
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +10 -3
- package/templates/apps/web/src/lib/commerce-claim.ts +15 -6
- package/templates/content/_site.mdx +23 -4
- package/templates/database/CHANGELOG.md +7 -0
- package/templates/database/package.json +1 -1
- package/versions.json +34 -33
- package/templates/.claude/skills/README.md +0 -37
- package/templates/.claude/skills/add-auth/SKILL.md +0 -313
- package/templates/.claude/skills/add-auth/references/env-vars.md +0 -106
- package/templates/.claude/skills/add-auth/references/oauth-app-setup.md +0 -180
- package/templates/.claude/skills/add-page/SKILL.md +0 -260
- package/templates/.claude/skills/add-page/references/page-types.md +0 -123
- package/templates/.claude/skills/add-page/scripts/site-pages.mjs +0 -87
- package/templates/.claude/skills/add-page/templates/comparison-page.mdx.tmpl +0 -99
- package/templates/.claude/skills/add-page/templates/content.mdx.tmpl +0 -18
- package/templates/.claude/skills/add-page/templates/flow-landing-page.mdx.tmpl +0 -43
- package/templates/.claude/skills/add-page/templates/report-page.mdx.tmpl +0 -52
- package/templates/.claude/skills/add-page/templates/reports-list.mdx.tmpl +0 -38
- package/templates/.claude/skills/check-site/SKILL.md +0 -245
- package/templates/.claude/skills/check-site/references/common-errors.md +0 -116
- package/templates/.claude/skills/check-site/references/findings-glossary.md +0 -140
- package/templates/.claude/skills/check-site/references/severity-levels.md +0 -114
- package/templates/.claude/skills/fill-page/SKILL.md +0 -199
- package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +0 -80
- package/templates/.claude/skills/fill-page/references/section-sources.md +0 -41
- package/templates/.claude/skills/setup-database/SKILL.md +0 -156
- package/templates/.claude/skills/setup-database/scripts/verify-inbox.ts +0 -154
- package/templates/.claude/skills/setup-deploy/SKILL.md +0 -312
- package/templates/.claude/skills/setup-deploy/references/runbook-template.md +0 -204
- package/templates/.claude/skills/setup-deploy/references/vercel-json-template.md +0 -98
- package/templates/.claude/skills/update-page/SKILL.md +0 -117
- package/templates/.claude/skills/update-page/scripts/update-page.ts +0 -640
- package/templates/.claude/skills/update-page/templates/redirects.mjs.tmpl +0 -31
- package/templates/.claude/skills/upgrade-plan/SKILL.md +0 -121
- package/templates/.claude/skills/upgrade-plan/references/options.md +0 -24
- package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +0 -527
- package/templates/apps/mcp/__tests__/lib/require-paid-tier.test.ts +0 -75
- package/templates/apps/mcp/src/lib/require-paid-tier.ts +0 -44
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// site-pages.mjs — report what THIS site can serve, read from the site itself.
|
|
3
|
-
//
|
|
4
|
-
// Usage: node .claude/skills/add-page/scripts/site-pages.mjs [--json] [<site-root>]
|
|
5
|
-
//
|
|
6
|
-
// It scans apps/web/app/[locale]/**/page.tsx for the page type each route
|
|
7
|
-
// renders (the `type: "X"` literal in renderPage / renderPageMetadata, or a
|
|
8
|
-
// getMdxSource("X", …) call), then pairs each type with its content folder and
|
|
9
|
-
// the pages already written there. It also echoes .ailk-site-manifest.json's
|
|
10
|
-
// shape, design and (when recorded) level. Read-only: it never writes.
|
|
11
|
-
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
12
|
-
import { join, relative, sep } from "node:path";
|
|
13
|
-
|
|
14
|
-
const args = process.argv.slice(2);
|
|
15
|
-
const asJson = args.includes("--json");
|
|
16
|
-
const root = args.find((a) => !a.startsWith("--")) ?? process.cwd();
|
|
17
|
-
const appRoot = join(root, "apps/web/app/[locale]");
|
|
18
|
-
if (!existsSync(appRoot)) {
|
|
19
|
-
console.error(`site-pages: ${appRoot} not found — run from the site's root.`);
|
|
20
|
-
process.exit(2);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Types whose content lives outside content/<locale>/<Type>.
|
|
24
|
-
const CONTENT_OVERRIDES = {
|
|
25
|
-
BlogPosting: "apps/web/content/en/blog",
|
|
26
|
-
BlogList: "apps/web/content/en/blog",
|
|
27
|
-
DocsDetail: "apps/web/content/en/docs",
|
|
28
|
-
DocsList: "apps/web/content/en/docs",
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
function walk(dir, out = []) {
|
|
32
|
-
for (const name of readdirSync(dir)) {
|
|
33
|
-
const abs = join(dir, name);
|
|
34
|
-
if (name === "__tests__" || name === "node_modules") continue;
|
|
35
|
-
if (statSync(abs).isDirectory()) walk(abs, out);
|
|
36
|
-
else if (name === "page.tsx") out.push(abs);
|
|
37
|
-
}
|
|
38
|
-
return out;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const routes = [];
|
|
42
|
-
for (const file of walk(appRoot)) {
|
|
43
|
-
const src = readFileSync(file, "utf8");
|
|
44
|
-
const types = new Set();
|
|
45
|
-
for (const m of src.matchAll(/type:\s*"([A-Z][A-Za-z]+)"/g)) types.add(m[1]);
|
|
46
|
-
for (const m of src.matchAll(/getMdxSource\(\s*"([A-Z][A-Za-z]+)"/g)) types.add(m[1]);
|
|
47
|
-
const dir = relative(appRoot, file).split(sep).slice(0, -1);
|
|
48
|
-
// Route groups like (auth) do not appear in the URL.
|
|
49
|
-
const path = "/" + dir.filter((s) => !/^\(.*\)$/.test(s)).join("/");
|
|
50
|
-
const dynamic = dir.some((s) => s.startsWith("["));
|
|
51
|
-
routes.push({ route: path === "/" ? "/" : path, file: relative(root, file), types: [...types], dynamic });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const byType = {};
|
|
55
|
-
for (const r of routes) {
|
|
56
|
-
for (const t of r.types) {
|
|
57
|
-
const entry = (byType[t] ??= { type: t, routes: [], contentDir: CONTENT_OVERRIDES[t] ?? `content/en/${t}`, pages: [] });
|
|
58
|
-
entry.routes.push(r.route);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
for (const e of Object.values(byType)) {
|
|
62
|
-
const abs = join(root, e.contentDir);
|
|
63
|
-
if (existsSync(abs)) {
|
|
64
|
-
e.pages = readdirSync(abs).filter((f) => /\.mdx?$/.test(f)).map((f) => f.replace(/\.mdx?$/, ""));
|
|
65
|
-
}
|
|
66
|
-
// A type served by a dynamic route takes new pages; a static-only type is one page.
|
|
67
|
-
e.takesNewPages = routes.some((r) => r.dynamic && r.types.includes(e.type));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
let manifest = null;
|
|
71
|
-
const manifestPath = join(root, ".ailk-site-manifest.json");
|
|
72
|
-
if (existsSync(manifestPath)) {
|
|
73
|
-
const m = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
74
|
-
manifest = { shape: m.shape ?? null, design: m.design ?? null, level: m.level ?? null, modules: m.modules ?? m.requires ?? null };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const result = { manifest, types: Object.values(byType).sort((a, b) => a.type.localeCompare(b.type)) };
|
|
78
|
-
if (asJson) {
|
|
79
|
-
console.log(JSON.stringify(result, null, 2));
|
|
80
|
-
} else {
|
|
81
|
-
if (manifest) console.log(`shape: ${manifest.shape} design: ${manifest.design} level: ${manifest.level ?? "(not recorded)"}`);
|
|
82
|
-
console.log("");
|
|
83
|
-
console.log("type".padEnd(22) + "route(s)".padEnd(34) + "new pages?".padEnd(12) + "existing");
|
|
84
|
-
for (const e of result.types) {
|
|
85
|
-
console.log(e.type.padEnd(22) + e.routes.join(" ").padEnd(34) + (e.takesNewPages ? "yes" : "no").padEnd(12) + (e.pages.join(", ") || "—"));
|
|
86
|
-
}
|
|
87
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: ComparisonPage
|
|
3
|
-
slug: {{SLUG}}
|
|
4
|
-
placeholder: true
|
|
5
|
-
title: {{TITLE}}
|
|
6
|
-
description: {{DESCRIPTION}}
|
|
7
|
-
sections:
|
|
8
|
-
[
|
|
9
|
-
{
|
|
10
|
-
"id": "hero",
|
|
11
|
-
"type": "hero",
|
|
12
|
-
"data":
|
|
13
|
-
{
|
|
14
|
-
"eyebrow": "Comparisons",
|
|
15
|
-
"headline": "{{TITLE}}",
|
|
16
|
-
"subheadline": "{{DESCRIPTION}}",
|
|
17
|
-
"primaryCta": { "label": "{Primary CTA}", "href": "/contact" },
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"id": "decision-split",
|
|
22
|
-
"type": "decision-split",
|
|
23
|
-
"data":
|
|
24
|
-
{
|
|
25
|
-
"headline": "{Which one fits you}",
|
|
26
|
-
"panels":
|
|
27
|
-
[
|
|
28
|
-
{
|
|
29
|
-
"title": "{{TITLE}}",
|
|
30
|
-
"checklist":
|
|
31
|
-
[
|
|
32
|
-
"{Reason 1 to choose us}",
|
|
33
|
-
"{Reason 2 to choose us}",
|
|
34
|
-
"{Reason 3 to choose us}",
|
|
35
|
-
],
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"title": "{The alternative}",
|
|
39
|
-
"glyph": "cross",
|
|
40
|
-
"checklist":
|
|
41
|
-
[
|
|
42
|
-
"{Reason 1 to choose them instead}",
|
|
43
|
-
"{Reason 2 to choose them instead}",
|
|
44
|
-
],
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"id": "comparison-table",
|
|
51
|
-
"type": "comparison-table",
|
|
52
|
-
"data":
|
|
53
|
-
{
|
|
54
|
-
"headline": "{How they compare}",
|
|
55
|
-
"columns":
|
|
56
|
-
[
|
|
57
|
-
{ "key": "us", "name": "{{TITLE}}", "highlighted": true },
|
|
58
|
-
{ "key": "them", "name": "{The alternative}" },
|
|
59
|
-
],
|
|
60
|
-
"groups":
|
|
61
|
-
[
|
|
62
|
-
{
|
|
63
|
-
"groupName": "{Core features}",
|
|
64
|
-
"rows":
|
|
65
|
-
[
|
|
66
|
-
{
|
|
67
|
-
"featureName": "{Feature 1}",
|
|
68
|
-
"values": { "us": true, "them": false },
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
"featureName": "{Feature 2}",
|
|
72
|
-
"values": { "us": true, "them": true },
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
"id": "cta",
|
|
81
|
-
"type": "cta",
|
|
82
|
-
"data":
|
|
83
|
-
{
|
|
84
|
-
"variant": "box",
|
|
85
|
-
"headline": "{Ready to switch?}",
|
|
86
|
-
"description": "{One sentence on what happens next}",
|
|
87
|
-
"primaryCta": { "label": "{Primary CTA}", "href": "/contact" },
|
|
88
|
-
"secondaryCta":
|
|
89
|
-
{
|
|
90
|
-
"label": "See all alternatives",
|
|
91
|
-
"href": "/alternatives",
|
|
92
|
-
"variant": "outline",
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
]
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
{/* Rendered from frontmatter `sections[]` by the /vs/<slug> route (D10 — the generic detail chassis: hero, decision-split, comparison-table, cta; no dedicated comparison section). Listed automatically by the /alternatives hub. Placeholder copy — replace every {brace} with your own words before you launch. */}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: {{TYPE}}
|
|
3
|
-
slug: {{SLUG}}
|
|
4
|
-
title: {{TITLE}}
|
|
5
|
-
description: {{DESCRIPTION}}
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
{/*
|
|
9
|
-
Written by the add-page skill, used only when this site had no other page of
|
|
10
|
-
this type to copy. The frontmatter drives the page's route and its JSON-LD:
|
|
11
|
-
keep `type` as it is, and make `description` a true one-sentence summary.
|
|
12
|
-
Run `fill-page` to write the real copy, then `check-site` to score it.
|
|
13
|
-
Extra structured data goes in a `schema:` block — see the add-page skill.
|
|
14
|
-
*/}
|
|
15
|
-
|
|
16
|
-
# {{TITLE}}
|
|
17
|
-
|
|
18
|
-
{{DESCRIPTION}}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: LandingPage
|
|
3
|
-
slug: {{SLUG}}
|
|
4
|
-
placeholder: true
|
|
5
|
-
title: {{TITLE}}
|
|
6
|
-
description: {{DESCRIPTION}}
|
|
7
|
-
sections:
|
|
8
|
-
[
|
|
9
|
-
{
|
|
10
|
-
"id": "hero",
|
|
11
|
-
"type": "hero",
|
|
12
|
-
"data":
|
|
13
|
-
{
|
|
14
|
-
"variant": "centered",
|
|
15
|
-
"eyebrow": "{{TITLE}}",
|
|
16
|
-
"headline": "{{TITLE}}",
|
|
17
|
-
"subheadline": "{{DESCRIPTION}}",
|
|
18
|
-
"primaryCta":
|
|
19
|
-
{ "label": "{Get started →}", "href": "/flows/{{FLOW_SLUG}}/start" },
|
|
20
|
-
"ctaSize": "xl",
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"id": "request",
|
|
25
|
-
"type": "cta",
|
|
26
|
-
"data":
|
|
27
|
-
{
|
|
28
|
-
"variant": "minimal",
|
|
29
|
-
"headline": "{Ready?}",
|
|
30
|
-
"primaryCta":
|
|
31
|
-
{ "label": "{Get started →}", "href": "/flows/{{FLOW_SLUG}}/start" },
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
]
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
{/* This page's CTA opens the `{{FLOW_SLUG}}` flow at
|
|
38
|
-
/flows/{{FLOW_SLUG}}/start — a signal-collectors module route, not a page
|
|
39
|
-
this seed owns. The flow definition itself
|
|
40
|
-
(`database/flows/{{FLOW_SLUG}}.flow.json`) is a separate artifact this
|
|
41
|
-
skill does not write; seed it via the flow engine before this page's CTA
|
|
42
|
-
resolves. Placeholder copy — replace every {brace} with your own words
|
|
43
|
-
before you launch. */}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: ReportPage
|
|
3
|
-
slug: {{SLUG}}
|
|
4
|
-
placeholder: true
|
|
5
|
-
title: {{TITLE}}
|
|
6
|
-
description: {{DESCRIPTION}}
|
|
7
|
-
schema:
|
|
8
|
-
datePublished: "{{DATE_PUBLISHED}}"
|
|
9
|
-
author:
|
|
10
|
-
"@type": Organization
|
|
11
|
-
name: "{Your Company}"
|
|
12
|
-
sections:
|
|
13
|
-
[
|
|
14
|
-
{
|
|
15
|
-
"id": "header",
|
|
16
|
-
"type": "report-header",
|
|
17
|
-
"data":
|
|
18
|
-
{
|
|
19
|
-
"title": "{{TITLE}}",
|
|
20
|
-
"eyebrow": "{{DATE_LABEL}}",
|
|
21
|
-
"backHref": "/reports",
|
|
22
|
-
"backLabel": "All reports",
|
|
23
|
-
"tone": "on-dark",
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"id": "document",
|
|
28
|
-
"type": "document-viewer",
|
|
29
|
-
"data":
|
|
30
|
-
{
|
|
31
|
-
"src": "/reports/sample-report.pdf",
|
|
32
|
-
"geometry": "16:9",
|
|
33
|
-
"pages": 8,
|
|
34
|
-
"downloadFilename": "{{SLUG}}.pdf",
|
|
35
|
-
"outline":
|
|
36
|
-
[
|
|
37
|
-
{ "id": "opening", "label": "Opening", "startPage": 1 },
|
|
38
|
-
{ "id": "findings", "label": "Findings", "startPage": 3 },
|
|
39
|
-
{ "id": "outlook", "label": "Outlook", "startPage": 6 },
|
|
40
|
-
],
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
]
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
{/* A PLACEHOLDER report. Its bytes are the signal-collectors module's own
|
|
47
|
-
sample PDF (apps/web/public/reports/sample-report.pdf), shared by every
|
|
48
|
-
sample report so the site ships no binary of its own. Point `src` at your
|
|
49
|
-
real document and set `pages` to its real page count. Rendered by the
|
|
50
|
-
signal-collectors module's own /reports/<slug> route (`app/[locale]/reports/[slug]/page.tsx`)
|
|
51
|
-
and listed automatically by the /reports hub (ReportsList) — never
|
|
52
|
-
hand-list a report there. */}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: ReportsList
|
|
3
|
-
slug: reports
|
|
4
|
-
placeholder: true
|
|
5
|
-
title: {{TITLE}}
|
|
6
|
-
description: {{DESCRIPTION}}
|
|
7
|
-
sections:
|
|
8
|
-
[
|
|
9
|
-
{
|
|
10
|
-
"id": "hero",
|
|
11
|
-
"type": "hero",
|
|
12
|
-
"data":
|
|
13
|
-
{
|
|
14
|
-
"variant": "centered",
|
|
15
|
-
"eyebrow": "Reports",
|
|
16
|
-
"headline": "{{TITLE}}",
|
|
17
|
-
"subheadline": "{{DESCRIPTION}}",
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"id": "closing",
|
|
22
|
-
"type": "cta",
|
|
23
|
-
"data":
|
|
24
|
-
{
|
|
25
|
-
"variant": "minimal",
|
|
26
|
-
"headline": "{Want the next one as it lands?}",
|
|
27
|
-
"primaryCta": { "label": "{Contact me →}", "href": "/contact" },
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
]
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
{/* Rendered from frontmatter `sections[]` by the /reports route
|
|
34
|
-
(`app/[locale]/reports/page.tsx`). Its card list is GENERATED, not
|
|
35
|
-
authored here (spec 6892 D2): the route appends one `reports-list`
|
|
36
|
-
section listing every ReportPage seed, so this seed carries only its own
|
|
37
|
-
hero and closing cta. Placeholder copy — replace every {brace} with your
|
|
38
|
-
own words before you launch. */}
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: check-site
|
|
3
|
-
description: Run `ailk-audit --format json` on the consumer's AILK site, parse the JSON report, and explain the score and findings in plain language. Use when the user asks "how is my site doing?", "audit my site", "what's my score?", "is my JSON-LD valid?", "do I have all the schema I need?", or otherwise wants a citation-readiness check on their AILK content. The skill never edits files — it reports and points at fix tools.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# check-site
|
|
7
|
-
|
|
8
|
-
Shell out to `ailk-audit --format json`, parse the JSON, and turn it into a
|
|
9
|
-
conversational summary the consumer can act on. Pure explain-only —
|
|
10
|
-
the skill names fix tools (like the `schema:` frontmatter override)
|
|
11
|
-
but does not invoke them.
|
|
12
|
-
|
|
13
|
-
## When to use
|
|
14
|
-
|
|
15
|
-
The user wants to know how their AILK site is doing on the
|
|
16
|
-
citation-readiness audit (AEO Layer 1). Triggers:
|
|
17
|
-
|
|
18
|
-
- "audit my site" / "run the audit"
|
|
19
|
-
- "what's my score?" / "how is my site doing?"
|
|
20
|
-
- "is my JSON-LD valid?" / "do I have schema on every page?"
|
|
21
|
-
- "what would I fix to bump my AEO score?"
|
|
22
|
-
- "re-run the audit, I just edited /pricing"
|
|
23
|
-
|
|
24
|
-
Skip this skill when:
|
|
25
|
-
|
|
26
|
-
- The consumer wants to _fix_ a finding — point them at the fix
|
|
27
|
-
tool (usually `add-page`'s `schema:` frontmatter override) and stop. This skill reports;
|
|
28
|
-
fix tools fix.
|
|
29
|
-
- The consumer is asking about deploy-time monitoring or continuous
|
|
30
|
-
audits — those are Pro features (the `upgrade-plan` skill explains
|
|
31
|
-
the levels). Mention briefly and stop.
|
|
32
|
-
- The audit CLI isn't installed (`apps/api/node_modules/.bin/ailk-audit`
|
|
33
|
-
is missing) — translate the error per "Common errors" below.
|
|
34
|
-
|
|
35
|
-
## The mechanic
|
|
36
|
-
|
|
37
|
-
### Step 0 — Read what this site can render
|
|
38
|
-
|
|
39
|
-
Read `.ailk-site-manifest.json` at the site root, the site's roster of
|
|
40
|
-
record:
|
|
41
|
-
|
|
42
|
-
- `modules` — the modules this site has. Add any module the
|
|
43
|
-
`# Modules:` line of `architecture.yaml` names that `modules` does not:
|
|
44
|
-
`create-ailk add-module` records a module it adds there. With no
|
|
45
|
-
manifest, or none carrying `modules`, that line is the set.
|
|
46
|
-
- `rows` — every route the site carries and the page type each renders.
|
|
47
|
-
- `level` — present only when the scaffold derived it from the licence
|
|
48
|
-
key. It never decides what to suggest; `modules` does.
|
|
49
|
-
|
|
50
|
-
A page type renders on this site only when its **Module** in
|
|
51
|
-
`.claude/skills/add-page/references/page-types.md` is in that set.
|
|
52
|
-
Step 7 suggests a page only of such a type.
|
|
53
|
-
|
|
54
|
-
### Step 1 — Invoke the CLI
|
|
55
|
-
|
|
56
|
-
The `ailk-audit` CLI ships in `@working-theory/aeo`, which the scaffold
|
|
57
|
-
installs as a dependency of `apps/api` — not of the site root — so pnpm
|
|
58
|
-
links its bin at `apps/api/node_modules/.bin/ailk-audit`. Run it **from
|
|
59
|
-
the site root**:
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
apps/api/node_modules/.bin/ailk-audit --format json
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
The site root matters: the CLI reads `content/` (its `--content-root`
|
|
66
|
-
default) and `project.yaml` relative to the directory it runs in. Do not
|
|
67
|
-
run it through `pnpm --filter ./apps/api exec`, which runs it inside
|
|
68
|
-
`apps/api` where neither exists. `--format json` is the flag that
|
|
69
|
-
selects JSON; the CLI rejects any flag it does not know and exits 2.
|
|
70
|
-
|
|
71
|
-
Capture both stdout (the JSON report) and stderr (errors). Do not
|
|
72
|
-
cache. Each invocation runs the CLI fresh.
|
|
73
|
-
|
|
74
|
-
### Step 2 — Detect errors first
|
|
75
|
-
|
|
76
|
-
If the CLI exited non-zero, treat as an error condition:
|
|
77
|
-
|
|
78
|
-
- If stderr matches one of the three common-error signatures, emit
|
|
79
|
-
the translated explanation from `references/common-errors.md`.
|
|
80
|
-
- Otherwise, surface the verbatim CLI stderr prefixed with one
|
|
81
|
-
line: `` `ailk-audit` failed — `` followed by the verbatim message.
|
|
82
|
-
Do not invent fix suggestions for unknown errors.
|
|
83
|
-
|
|
84
|
-
Stop here on errors. Do not continue to Step 3.
|
|
85
|
-
|
|
86
|
-
### Step 3 — Parse the JSON report
|
|
87
|
-
|
|
88
|
-
The report is the `AuditReport` type `@working-theory/aeo` exports
|
|
89
|
-
(its shipped `dist/types.d.ts`, under
|
|
90
|
-
`apps/api/node_modules/@working-theory/aeo/`):
|
|
91
|
-
|
|
92
|
-
```ts
|
|
93
|
-
interface AuditReport {
|
|
94
|
-
score: number; // 0–100
|
|
95
|
-
perRule: PerRuleResult[]; // one entry per rule, with id + pass + weight
|
|
96
|
-
perPage: PerPageResult[]; // one entry per audited page
|
|
97
|
-
meta: { schemaVersion; registryVersion; auditedAt };
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
`perRule` entries each have `{ id, weight, pass, evidence?, perPage? }`.
|
|
102
|
-
|
|
103
|
-
### Step 4 — Render the headline
|
|
104
|
-
|
|
105
|
-
The first line of output should be the score, in a compact form:
|
|
106
|
-
|
|
107
|
-
```
|
|
108
|
-
AEO score: 87 / 100
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
If the score is the same as the consumer's previous run (and you
|
|
112
|
-
can tell — but you usually can't, since this is always-fresh), do
|
|
113
|
-
not annotate with deltas. The audit doesn't track history.
|
|
114
|
-
|
|
115
|
-
### Step 5 — Group findings by severity
|
|
116
|
-
|
|
117
|
-
Severity is **derived from rule weight + pass/fail** per
|
|
118
|
-
`references/severity-levels.md`. The mapping at v1:
|
|
119
|
-
|
|
120
|
-
| Severity | Source |
|
|
121
|
-
| -------- | ------------------------------------------------------------------------------------------ |
|
|
122
|
-
| Critical | `pass: false` on a rule with `weight >= 10` (e.g., schema-coverage, jsonld-validity) |
|
|
123
|
-
| Warning | `pass: false` on a rule with `weight` in `[1, 9]` (e.g., entity-markup) |
|
|
124
|
-
| Info | `pass: true` on incentive rules (`weight: 0`) — these are positive signals; never failures |
|
|
125
|
-
|
|
126
|
-
After the score line, emit a one-line severity summary:
|
|
127
|
-
|
|
128
|
-
```
|
|
129
|
-
2 critical · 1 warning · 0 info
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
If the count for a tier is 0, omit it from the summary (e.g.,
|
|
133
|
-
"2 critical" if warnings and info are both empty).
|
|
134
|
-
|
|
135
|
-
### Step 6 — Drill-down on demand
|
|
136
|
-
|
|
137
|
-
Wait for the consumer's next message. Common follow-ups:
|
|
138
|
-
|
|
139
|
-
- "tell me about the critical findings" → emit each critical
|
|
140
|
-
finding's plain-language explanation (from
|
|
141
|
-
`references/findings-glossary.md`) plus, if applicable, a
|
|
142
|
-
pointer to the fix tool (see Step 7).
|
|
143
|
-
- "what about the warnings?" → same, for warnings.
|
|
144
|
-
- "show me the per-page scores" → emit the `perPage` list,
|
|
145
|
-
formatted as a table or bullet list.
|
|
146
|
-
- "re-run it" / "audit again" → go back to Step 1.
|
|
147
|
-
|
|
148
|
-
If the consumer doesn't ask anything specific, ask them which
|
|
149
|
-
severity tier they'd like to see first, and offer to point at the
|
|
150
|
-
likely fix-tool path.
|
|
151
|
-
|
|
152
|
-
### Step 7 — Map findings to fix tools (no auto-dispatch)
|
|
153
|
-
|
|
154
|
-
When you describe a finding, name the fix tool by name. Do not
|
|
155
|
-
invoke it. The consumer must explicitly invoke the fix tool
|
|
156
|
-
themselves on a subsequent turn.
|
|
157
|
-
|
|
158
|
-
| Rule | Fix tool |
|
|
159
|
-
| --------------------- | ---------------------------------------------------------------------------------------- |
|
|
160
|
-
| `schema-coverage` | `add-page`'s `schema:` frontmatter override — add the missing schema type to the named page |
|
|
161
|
-
| `jsonld-validity` | `add-page`'s `schema:` frontmatter override — replace the invalid block; or hand-fix the MDX |
|
|
162
|
-
| `entity-markup` | Manual edit — ensure `Organization` (or `Person`) JSON-LD is emitted somewhere site-wide |
|
|
163
|
-
| `faq-howto-incentive` | `add-page`'s `schema:` frontmatter override — add an `FAQPage` or `HowTo` block to a relevant page |
|
|
164
|
-
|
|
165
|
-
When a fix means adding a page, name only a page type this site can
|
|
166
|
-
render (Step 0) — for `faq-howto-incentive`, a `FAQPage` or `HowTo`
|
|
167
|
-
page, never one the site's modules do not render. When only a type the
|
|
168
|
-
site cannot render would fix a finding, say which module renders it and
|
|
169
|
-
which level carries it, and point at the `upgrade-plan` skill.
|
|
170
|
-
|
|
171
|
-
Phrase the pointer as a single sentence:
|
|
172
|
-
|
|
173
|
-
> "Missing `Product` schema on `/pricing` — add it via `add-page`'s
|
|
174
|
-
> `schema:` frontmatter override."
|
|
175
|
-
|
|
176
|
-
Never write to the consumer's repo from inside this skill.
|
|
177
|
-
|
|
178
|
-
### Step 8 — Soft Pro mention
|
|
179
|
-
|
|
180
|
-
If a finding maps to a Pro auto-fix capability, surface a
|
|
181
|
-
single-line mention tied to that finding:
|
|
182
|
-
|
|
183
|
-
- **Schema-pack drift** (a finding indicating a registry version
|
|
184
|
-
mismatch or stale schema-type usage):
|
|
185
|
-
|
|
186
|
-
> "Pro auto-applies schema-pack updates as schema.org evolves —
|
|
187
|
-
> the `upgrade-plan` skill explains the levels."
|
|
188
|
-
|
|
189
|
-
- **Continuous-deploy audit** (the consumer is asking how to keep
|
|
190
|
-
the score from regressing, not about a specific finding):
|
|
191
|
-
> "Pro can run this audit on every deploy and gate releases on
|
|
192
|
-
> the score — the `upgrade-plan` skill explains the levels."
|
|
193
|
-
|
|
194
|
-
Never produce a multi-line Pro recommendation, feature
|
|
195
|
-
comparison, or upgrade prompt. The mention is informational, not
|
|
196
|
-
salesy.
|
|
197
|
-
|
|
198
|
-
### Step 9 — Cadence guidance
|
|
199
|
-
|
|
200
|
-
If the consumer asks "how often should I run this?", answer:
|
|
201
|
-
|
|
202
|
-
> "Re-run after content changes — adding pages, editing schema,
|
|
203
|
-
> changing your identity entity. There's no calendar cadence — the
|
|
204
|
-
> audit reports current state, not trends."
|
|
205
|
-
|
|
206
|
-
Never recommend "weekly" / "monthly" / a date.
|
|
207
|
-
|
|
208
|
-
## Output style
|
|
209
|
-
|
|
210
|
-
- **Score line first.** No preamble before the number.
|
|
211
|
-
- **Plain language for findings.** Don't paste raw rule IDs at the
|
|
212
|
-
consumer; translate via `references/findings-glossary.md`.
|
|
213
|
-
- **Pointers, not pitches.** A finding becomes a sentence; a fix
|
|
214
|
-
tool becomes a sentence. The consumer drives the next turn.
|
|
215
|
-
- **Verbatim for unknown errors.** If something fails in a way
|
|
216
|
-
this skill doesn't recognize, surface the raw stderr and stop.
|
|
217
|
-
|
|
218
|
-
## What this skill does NOT do
|
|
219
|
-
|
|
220
|
-
- Edit any file in the consumer's repo.
|
|
221
|
-
- Add JSON-LD to pages (that's the `schema:` frontmatter override, documented in `add-page`).
|
|
222
|
-
- Create new pages (that's `add-page`).
|
|
223
|
-
- Cache results between runs.
|
|
224
|
-
- Track score history or trends.
|
|
225
|
-
- Auto-invoke fix tools.
|
|
226
|
-
- Pitch Pro hard.
|
|
227
|
-
|
|
228
|
-
## References
|
|
229
|
-
|
|
230
|
-
- `references/findings-glossary.md` — plain-language explanation per rule.
|
|
231
|
-
- `references/common-errors.md` — translations for the three most common CLI failures.
|
|
232
|
-
- `references/severity-levels.md` — how rule weight + pass/fail maps to severity.
|
|
233
|
-
|
|
234
|
-
## Coordination
|
|
235
|
-
|
|
236
|
-
- **`add-page`'s `schema:` frontmatter override** — primary fix
|
|
237
|
-
mechanism the skill points at. Never auto-invoked.
|
|
238
|
-
- **`add-page`** — new-page creation; not a fix-tool
|
|
239
|
-
for audit findings (audit reports on existing pages).
|
|
240
|
-
- **`@working-theory/aeo`** — owner of the `ailk-audit` CLI and rule
|
|
241
|
-
set, installed under `apps/api`. This skill depends only on the
|
|
242
|
-
CLI's `--format json` output schema, not the package's internal
|
|
243
|
-
types. New rules added upstream surface
|
|
244
|
-
automatically as new findings; the glossary may lag and that's
|
|
245
|
-
acceptable (unrecognized rule IDs render as the raw ID).
|