create-ailk 0.10.0 → 0.11.1
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/lib/scaffold-render-sweep.d.ts +16 -0
- package/dist/lib/scaffold-render-sweep.js +52 -3
- 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/site-templates/shapes/local-business-service/content/en/Service/first-job.mdx +8 -8
- package/site-templates/shapes/local-business-service/content/en/Service/second-job.mdx +8 -8
- package/site-templates/shapes/local-business-service/content/en/Service/third-job.mdx +8 -8
- package/site-templates/shapes/organization-service/content/en/Service/first-engagement.mdx +8 -8
- package/site-templates/shapes/organization-service/content/en/Service/second-engagement.mdx +8 -8
- package/site-templates/shapes/organization-service/content/en/Service/third-engagement.mdx +8 -8
- package/site-templates/shapes/person-service/content/en/Service/first-engagement.mdx +8 -8
- package/site-templates/shapes/person-service/content/en/Service/second-engagement.mdx +8 -8
- package/site-templates/shapes/person-service/content/en/Service/third-engagement.mdx +8 -8
- package/templates/.claude/settings.json +12 -0
- package/templates/.env.example +1 -5
- 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 +2 -1
- 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/package.json +1 -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/CLAUDE.md +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/app/[locale]/careers/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +1 -14
- package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/features/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/for/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +1 -14
- package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +1 -15
- package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +1 -14
- package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +4 -16
- package/templates/apps/web/app/[locale]/vs/[slug]/__tests__/page.test.tsx +3 -15
- package/templates/apps/web/app/[locale]/vs/[slug]/page.tsx +0 -10
- package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +1 -14
- 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 -34
- 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,140 +0,0 @@
|
|
|
1
|
-
# Findings Glossary — Per-Rule Plain-Language Explanations
|
|
2
|
-
|
|
3
|
-
What each AEO rule means in plain language, what its `pass: false`
|
|
4
|
-
result indicates, and what the consumer can do about it. Used by the
|
|
5
|
-
`check-site` skill in Step 6 (drill-down) and Step 7 (fix-tool
|
|
6
|
-
pointer).
|
|
7
|
-
|
|
8
|
-
The structure deliberately matches `perRule[].id` from the audit
|
|
9
|
-
JSON so the skill can grep this file for `## <rule-id>` to find the
|
|
10
|
-
explanation. New rules added to `@working-theory/aeo` will not appear here
|
|
11
|
-
until this file is updated; render unrecognized rule IDs as the raw
|
|
12
|
-
ID (no synthesis).
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## schema-coverage
|
|
17
|
-
|
|
18
|
-
**Plain language:** every page on your site should emit at least
|
|
19
|
-
one JSON-LD object whose `@type` matches what the page is _about_.
|
|
20
|
-
A `/pricing` page should have `Product` (or `Service`, or
|
|
21
|
-
`OfferCatalog`) schema; a `/about` page should have `AboutPage`;
|
|
22
|
-
and so on.
|
|
23
|
-
|
|
24
|
-
**`pass: false` means:** one or more pages emit no JSON-LD whose
|
|
25
|
-
`@type` matches the registry's expected `pageType` for that page's
|
|
26
|
-
content type. The `evidence` field names which pages.
|
|
27
|
-
|
|
28
|
-
**Why it matters:** structured data is what answer engines (Google
|
|
29
|
-
SGE, Perplexity, ChatGPT search) read to understand your pages. A
|
|
30
|
-
page with no schema is invisible to citation; a page with the
|
|
31
|
-
wrong schema gets miscategorized.
|
|
32
|
-
|
|
33
|
-
**Fix tool:** `add-page`'s `schema:` frontmatter override — set the
|
|
34
|
-
schema type the registry expects (the audit names it) on the page's
|
|
35
|
-
frontmatter. The typed builder emits a `<JsonLd>` block matching the
|
|
36
|
-
page's content.
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## jsonld-validity
|
|
41
|
-
|
|
42
|
-
**Plain language:** every JSON-LD block on the site should be
|
|
43
|
-
well-formed schema.org markup with all required fields present
|
|
44
|
-
and non-empty for its `@type`. Missing the `name` on an
|
|
45
|
-
`Organization`, missing `headline` on an `Article`, etc., breaks
|
|
46
|
-
schema validity.
|
|
47
|
-
|
|
48
|
-
**`pass: false` means:** at least one JSON-LD block has missing or
|
|
49
|
-
empty schema.org-required fields for its declared `@type`.
|
|
50
|
-
|
|
51
|
-
**Why it matters:** answer engines and crawlers may silently drop
|
|
52
|
-
invalid blocks. The page exists, the JSON-LD exists, but the
|
|
53
|
-
citation surface is broken because the block doesn't parse as
|
|
54
|
-
valid schema.org.
|
|
55
|
-
|
|
56
|
-
**Fix tool:** `add-page`'s `schema:` frontmatter override — replace
|
|
57
|
-
the malformed block. For one-off edits to MDX, the consumer can also
|
|
58
|
-
hand-edit the JSON-LD inline; the frontmatter override is the safer
|
|
59
|
-
path because it uses typed builders.
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## entity-markup
|
|
64
|
-
|
|
65
|
-
**Plain language:** somewhere on your site (typically the home
|
|
66
|
-
page or layout root), there should be exactly one identity entity
|
|
67
|
-
JSON-LD — `Organization` if the site represents a business, or
|
|
68
|
-
`Person` if the site represents an individual (a solo lawyer,
|
|
69
|
-
realtor, consultant, author, etc.). The audit picks up on
|
|
70
|
-
`identityMode` (configured in `ailk.config.ts`).
|
|
71
|
-
|
|
72
|
-
**`pass: false` means:** no `Organization` block (when
|
|
73
|
-
`identityMode: organization`) or no `Person` block (when
|
|
74
|
-
`identityMode: person`) was emitted by any audited page.
|
|
75
|
-
|
|
76
|
-
**Why it matters:** the identity entity is what ties every other
|
|
77
|
-
piece of structured data together. Without it, schema.org
|
|
78
|
-
relationships (`isPartOf`, `author`, `provider`, `mainEntity`)
|
|
79
|
-
have nothing to anchor to.
|
|
80
|
-
|
|
81
|
-
**Fix:** Manual MDX or layout edit — ensure your home page (or a
|
|
82
|
-
layout component rendered on every page) emits an
|
|
83
|
-
`Organization` (or `Person`) JSON-LD block. The `<JsonLd
|
|
84
|
-
schema={OrganizationSchema(...)} />` pattern from `@working-theory/schema`
|
|
85
|
-
is the one this is going to ship as. `add-page`'s `schema:`
|
|
86
|
-
frontmatter override can land the block on a specific page if you'd
|
|
87
|
-
rather; but typically
|
|
88
|
-
identity goes in a layout, not a leaf page.
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## faq-howto-incentive
|
|
93
|
-
|
|
94
|
-
**Plain language:** pages that emit `FAQPage` or `HowTo` schema
|
|
95
|
-
get a small bonus signal. This rule **never fails a page** — it's
|
|
96
|
-
purely incentivizing. Absence of FAQ/HowTo blocks is neutral, not
|
|
97
|
-
a failure.
|
|
98
|
-
|
|
99
|
-
**`pass: true` and `bonus: true` per page means:** that page
|
|
100
|
-
earned the incentive (has FAQ or HowTo schema).
|
|
101
|
-
|
|
102
|
-
**`pass: true` and `bonus: false` per page means:** that page is
|
|
103
|
-
fine, just didn't earn the bonus.
|
|
104
|
-
|
|
105
|
-
**Why it matters:** answer engines preferentially cite pages with
|
|
106
|
-
explicit Q&A or step-by-step content because those are easier to
|
|
107
|
-
quote directly into a generated response. Adding an FAQ or HowTo
|
|
108
|
-
block to relevant pages is a low-effort score boost.
|
|
109
|
-
|
|
110
|
-
**Fix tool:** `add-page`'s `schema:` frontmatter override — add an
|
|
111
|
-
`FAQPage` block to your `/pricing` or `/contact` page (anywhere your
|
|
112
|
-
customers ask predictable questions), or a `HowTo` block to any
|
|
113
|
-
process/instructions content. `check-site` will surface this as
|
|
114
|
-
an _incentive_ in drill-down, not a finding to "fix."
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## always-passes / always-fails
|
|
119
|
-
|
|
120
|
-
These are **test rules** that exist for the audit's own test suite
|
|
121
|
-
in `@working-theory/aeo`. They should not appear on a real consumer site
|
|
122
|
-
unless the rule list has been customized. If you see one of these
|
|
123
|
-
IDs in your audit output, the consumer's `ailk-audit` CLI may have
|
|
124
|
-
a non-standard rule list — that's a setup question, not a content
|
|
125
|
-
finding.
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
## Unrecognized rule IDs
|
|
130
|
-
|
|
131
|
-
If the audit JSON includes a rule ID not listed above, the rule is
|
|
132
|
-
new (added to `@working-theory/aeo` after this glossary was last updated).
|
|
133
|
-
Render it as:
|
|
134
|
-
|
|
135
|
-
> Rule `<id>` — `<evidence text from the audit JSON>` — not in
|
|
136
|
-
> the local glossary; consult the `@working-theory/aeo` package docs.
|
|
137
|
-
|
|
138
|
-
Do not invent a plain-language explanation for an unrecognized
|
|
139
|
-
rule. The consumer can read the rule's `description` field from
|
|
140
|
-
the JSON if they want the formal definition.
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# Severity Levels — Score Banding + Severity Mapping
|
|
2
|
-
|
|
3
|
-
The AEO audit JSON does **not** include explicit severity fields.
|
|
4
|
-
Severity is **derived** by the `check-site` skill from each rule's
|
|
5
|
-
`weight` and `pass`. This file is the source of truth for that
|
|
6
|
-
mapping.
|
|
7
|
-
|
|
8
|
-
The skill applies these rules in `SKILL.md` Step 5.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Severity tiers
|
|
13
|
-
|
|
14
|
-
| Tier | Source signal | Meaning |
|
|
15
|
-
| -------- | ----------------------------------------------- | ----------------------------------------------------------------------- |
|
|
16
|
-
| Critical | `pass: false` AND `weight >= 10` | Rule that materially affects citation readiness; failure is blocking. |
|
|
17
|
-
| Warning | `pass: false` AND `weight` in `[1, 9]` | Rule that nudges citation quality; failure is fixable, not blocking. |
|
|
18
|
-
| Info | `pass: true` AND `weight: 0` (incentive rules) | Positive signal earned. Bonus, not a finding. |
|
|
19
|
-
| Neutral | `pass: true` AND `weight: 0` AND `bonus: false` | Page is fine but didn't earn the incentive. Don't surface unless asked. |
|
|
20
|
-
|
|
21
|
-
A rule with `pass: false` AND `weight: 0` is **not possible** today
|
|
22
|
-
(incentive rules are designed to never fail — see
|
|
23
|
-
`faq-howto-incentive` in `@working-theory/aeo`). If one ever appears,
|
|
24
|
-
treat it as Info (the rule's own design says failures are neutral).
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## Why weight thresholds, not explicit severity
|
|
29
|
-
|
|
30
|
-
The AEO type system intentionally keeps rules weight-based, not
|
|
31
|
-
severity-based. Reasons:
|
|
32
|
-
|
|
33
|
-
- **Score arithmetic stays simple.** `passing-weight / total-weight`
|
|
34
|
-
is the score; introducing severity as a separate axis would force
|
|
35
|
-
every rule to choose two attributes (weight AND severity) when
|
|
36
|
-
one is enough.
|
|
37
|
-
- **The skill is the right place for the translation.** Consumers
|
|
38
|
-
don't care about weights — they care about "should I fix this?".
|
|
39
|
-
The skill turns "weight 10 fail" into "Critical" because that's
|
|
40
|
-
the language consumers can act on.
|
|
41
|
-
- **Rule authors don't have to think about severity.** A new rule
|
|
42
|
-
added to `@working-theory/aeo` gets a weight (its impact on the score)
|
|
43
|
-
and the skill maps that to a severity tier automatically. No
|
|
44
|
-
coupling between rule authoring and consumer-facing UX.
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## Score banding (optional)
|
|
49
|
-
|
|
50
|
-
When the consumer asks "is this a good score?", you can map the
|
|
51
|
-
numeric score to a band. This is optional — Step 4 always emits
|
|
52
|
-
the raw score first.
|
|
53
|
-
|
|
54
|
-
| Score | Band | Hint |
|
|
55
|
-
| ------ | ----------------- | ---------------------------------------------------------------- |
|
|
56
|
-
| 90–100 | Excellent | All critical rules pass; site is well-instrumented for citation. |
|
|
57
|
-
| 75–89 | Good | Probably a critical or warning fixable on a couple of pages. |
|
|
58
|
-
| 50–74 | Needs work | Multiple critical rules failing; address those first. |
|
|
59
|
-
| 0–49 | Foundational gaps | Site likely has no schema or invalid schema across most pages. |
|
|
60
|
-
|
|
61
|
-
Hint sentences are suggestions; adapt phrasing to the consumer's
|
|
62
|
-
context. Don't quote the band tag verbatim if a more natural
|
|
63
|
-
phrasing fits the conversation ("you're at 87 — couple of fixes
|
|
64
|
-
away from clean").
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## Severity counting for the summary line
|
|
69
|
-
|
|
70
|
-
Step 5 of SKILL.md asks for a summary like:
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
2 critical · 1 warning · 0 info
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Counts are computed as:
|
|
77
|
-
|
|
78
|
-
- **Critical count** = count of `perRule[]` entries where
|
|
79
|
-
`pass === false && weight >= 10`.
|
|
80
|
-
- **Warning count** = count of `perRule[]` entries where
|
|
81
|
-
`pass === false && weight >= 1 && weight < 10`.
|
|
82
|
-
- **Info count** = count of `perRule[]` entries where
|
|
83
|
-
`pass === true && weight === 0` (i.e., earned incentives).
|
|
84
|
-
|
|
85
|
-
Some rules surface per-page failures (`perRule[].perPage[]`).
|
|
86
|
-
Those are deeper-level findings the skill can drill into in Step 6;
|
|
87
|
-
they don't change the rule-level severity counts. A `schema-coverage`
|
|
88
|
-
rule with `pass: false` and 5 failing pages is **one** critical
|
|
89
|
-
finding (the rule failed), with 5 page-specific evidence entries
|
|
90
|
-
for drill-down.
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## What this skill does NOT classify as severity
|
|
95
|
-
|
|
96
|
-
- **`bonus: true` per page** is not a finding; it's a positive
|
|
97
|
-
signal. Surface it on drill-down ("These pages earned the FAQ
|
|
98
|
-
bonus: …") but don't include it in the severity summary.
|
|
99
|
-
- **Schema-version mismatch** (a `meta.schemaVersion` from the
|
|
100
|
-
audit that doesn't match the consumer's `@working-theory/schema`) is not a
|
|
101
|
-
rule failure — it's a setup-drift signal. Surface only if the
|
|
102
|
-
consumer asks why the score might be inconsistent across runs.
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
## Future rules
|
|
107
|
-
|
|
108
|
-
When `@working-theory/aeo` adds a new rule, this glossary may not name
|
|
109
|
-
it. The severity mapping still works because it keys off `weight`
|
|
110
|
-
and `pass` only — both fields are guaranteed by the
|
|
111
|
-
`PerRuleResult` type. New critical-tier rules will surface as
|
|
112
|
-
`Critical` automatically; new warnings as `Warning`. The findings
|
|
113
|
-
glossary may lag in offering plain-language explanations until
|
|
114
|
-
this template is updated.
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: fill-page
|
|
3
|
-
description: Fill one existing page's section content from a category profile. Tier 1 writes realistic, labelled sample content for the category (a plumber's home page, say). Tier 2 asks the owner the profile's questions and writes the company's own first draft from their answers and `_site.mdx`. Use when the user says "fill this page", "fill the home page for a plumber", "write sample content for my services page", or "write my real copy from what I tell you". It never creates a page, never changes a page's section stack, and never edits `_site.mdx`.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# fill-page
|
|
7
|
-
|
|
8
|
-
Replace one page's `{brace}` instructions with content, reading the
|
|
9
|
-
content from a **category profile** (`profiles/<slug>.yaml` beside this
|
|
10
|
-
file). A profile is the data for one business type: the schema.org
|
|
11
|
-
subtype, the typical offerings, the proof buyers expect, the buyer's own
|
|
12
|
-
words, and the questions to ask the owner.
|
|
13
|
-
|
|
14
|
-
Two tiers:
|
|
15
|
-
|
|
16
|
-
| Tier | Source | Result | `placeholder` |
|
|
17
|
-
| ---- | -------------------------------------------------------- | ------------------------------------------ | ------------- |
|
|
18
|
-
| 1 | the profile | realistic sample content for the category | `true` |
|
|
19
|
-
| 2 | `_site.mdx` + the owner's answers (`content/_intake.yaml`) | the company's own first draft | by the rule in Step 2.4 |
|
|
20
|
-
|
|
21
|
-
`placeholder: true` in a page's frontmatter makes the site render a
|
|
22
|
-
**PLACEHOLDER — REPLACE BEFORE LAUNCH** label at the top of the page.
|
|
23
|
-
The label is for people. Structured data reaches agents without it, so
|
|
24
|
-
tier-1 content must be safe to publish by itself (see the never-invent
|
|
25
|
-
list).
|
|
26
|
-
|
|
27
|
-
## Inputs
|
|
28
|
-
|
|
29
|
-
- **The page file** — `content/<locale>/<Type>/<slug>.mdx`. It must
|
|
30
|
-
already exist; `add-page` creates pages.
|
|
31
|
-
- **The page type** — the frontmatter `type`.
|
|
32
|
-
- **The profile slug** — a file under `profiles/` (e.g. `plumbing`).
|
|
33
|
-
Ask if the user did not name one. If there is no profile for their
|
|
34
|
-
category, say so and stop.
|
|
35
|
-
- **The tier** — 1 unless the user asks for their own copy.
|
|
36
|
-
|
|
37
|
-
## What never changes
|
|
38
|
-
|
|
39
|
-
- The page's **section stack**: every section's `id` and `type`, in
|
|
40
|
-
order. Never add, remove or reorder a section. The starter's manifest
|
|
41
|
-
records each page's sections, and a changed stack fails its check.
|
|
42
|
-
- Each section's **layout options** that are already present:
|
|
43
|
-
`variant`, `columns`, `layout`, `initialOpen`, `size` and the like.
|
|
44
|
-
They are the site's locked look.
|
|
45
|
-
- Link targets (`href`) that are already present, except a link to a
|
|
46
|
-
Service page, which follows that page's slug (Step 1.3).
|
|
47
|
-
- `{/* … */}` comments in the MDX body.
|
|
48
|
-
- `_site.mdx`. Identity strings (the company name) are read from it,
|
|
49
|
-
never written to it.
|
|
50
|
-
|
|
51
|
-
## Never invent (tier 1)
|
|
52
|
-
|
|
53
|
-
At tier 1, write none of these. Remove any you find in the page:
|
|
54
|
-
|
|
55
|
-
- `price`, `priceCurrency`, `availability` or `stripe` — each feeds the
|
|
56
|
-
schema.org `Offer` an agent quotes as the company's price.
|
|
57
|
-
- A person's name or another company's name.
|
|
58
|
-
- A phone number, a street address, or a licence or registration number.
|
|
59
|
-
- A town, a county, or a distance: the service area is a company fact.
|
|
60
|
-
- Testimonials: `author` is the literal `Sample customer`; no `company`,
|
|
61
|
-
`rating` or `avatar`.
|
|
62
|
-
- Statistics a reader would take as measured (a count of jobs, a years
|
|
63
|
-
figure). Use the profile's `proof[]` labels as words, not numbers.
|
|
64
|
-
|
|
65
|
-
The company name is the one identity string tier 1 may write, and it
|
|
66
|
-
comes from `_site.mdx` `siteIdentity.name`.
|
|
67
|
-
|
|
68
|
-
## Step 0 — Read
|
|
69
|
-
|
|
70
|
-
1. Read `.ailk-site-manifest.json` at the site root, the site's roster
|
|
71
|
-
of record: `modules` (the modules this site has) and `rows` (every
|
|
72
|
-
route it carries). Add any module the `# Modules:` line of
|
|
73
|
-
`architecture.yaml` names that `modules` does not — `create-ailk
|
|
74
|
-
add-module` records a module it adds there; with no manifest, or none
|
|
75
|
-
carrying `modules`, that line is the set. `level`, when present, never
|
|
76
|
-
decides anything here.
|
|
77
|
-
|
|
78
|
-
The page's `type` must render on this site: its **Module** in
|
|
79
|
-
`../add-page/references/page-types.md` is in that set, or a row
|
|
80
|
-
already renders it (`rows[].pageType` — a Free starter's `/meet/<slug>`
|
|
81
|
-
`BookCall` placeholders). When it does not,
|
|
82
|
-
say which module renders the type and which level carries it, point at
|
|
83
|
-
the `upgrade-plan` skill, and stop. Never fill a page this site cannot
|
|
84
|
-
render.
|
|
85
|
-
2. Read the profile: `profiles/<slug>.yaml`.
|
|
86
|
-
3. Read `content/_site.mdx` for `siteIdentity.name`.
|
|
87
|
-
4. Read the page file. Note its stack (`sections[].type` in order).
|
|
88
|
-
5. For each section type in the stack, read its data schema: the
|
|
89
|
-
`<Type>SectionDataSchema` (or `<Type>DataSchema`) export in the
|
|
90
|
-
installed `@working-theory/validation`'s `schemas/page-sections`. Fill
|
|
91
|
-
only fields the schema declares. Every field a section renders from
|
|
92
|
-
copy gets content; a field the source page did not use stays unused.
|
|
93
|
-
6. Find the route that renders this page type under `apps/web/app/[locale]/`
|
|
94
|
-
and read its `bodyMode`. When it is `"mdx-body"`, the MDX body is
|
|
95
|
-
rendered and must be filled too. Otherwise the body is a short note,
|
|
96
|
-
and you rewrite it as one plain sentence that names no braces.
|
|
97
|
-
|
|
98
|
-
`references/section-sources.md` maps each section type to the profile
|
|
99
|
-
fields and intake answers that fill it.
|
|
100
|
-
|
|
101
|
-
## Step 1 — Tier 1: sample content
|
|
102
|
-
|
|
103
|
-
1. **Sections.** For each section, in place, write every copy field from
|
|
104
|
-
the profile using `references/section-sources.md`:
|
|
105
|
-
- offerings → `items` entries (`title` = the offering `name`,
|
|
106
|
-
`description` = its `job`, `link.href` = `/services/<kebab name>`
|
|
107
|
-
only when the manifest's `rows` carry `/services/[slug]`; otherwise
|
|
108
|
-
no `link`, so no entry points at a page the site does not have);
|
|
109
|
-
- vocabulary → headlines, eyebrows and FAQ questions (the buyer's own
|
|
110
|
-
words: the `problem` list for what went wrong, `outcome` for what
|
|
111
|
-
they get);
|
|
112
|
-
- proof → `stats` labels and `testimonials` quotes, under the
|
|
113
|
-
never-invent list.
|
|
114
|
-
Keep `id`, `type` and every layout option.
|
|
115
|
-
2. **Frontmatter.** Rewrite `title` and `description` without braces.
|
|
116
|
-
Use the company name from `_site.mdx` where the page names the
|
|
117
|
-
company. On a page that carries `provider.name`, set it to the company
|
|
118
|
-
name. Drop `schema.areaServed`; set `schema.serviceType` to the
|
|
119
|
-
offering name. Remove every key the never-invent list names.
|
|
120
|
-
3. **Service pages.** A `Service` page is one offering. The user names
|
|
121
|
-
the offering, or you take the first profile offering that no other
|
|
122
|
-
Service page carries yet. Set `title` and `schema.serviceType` to the
|
|
123
|
-
offering `name`, and `slug` and the file name to its name in kebab
|
|
124
|
-
case (`Drain clearing` → `drain-clearing`): rename the file, never
|
|
125
|
-
copy it. To give every offering a page, the user first adds one
|
|
126
|
-
Service page per offering (`add-page`, or a copy of an existing one),
|
|
127
|
-
then runs this skill on each.
|
|
128
|
-
4. **Body.** When the route renders `mdx-body`, rewrite each paragraph
|
|
129
|
-
and list item under the page's existing headings in the category's
|
|
130
|
-
words. Keep the headings, keep the order, keep the comments. Drop
|
|
131
|
-
every `\{…\}` instruction. Under a price heading, say how the job is
|
|
132
|
-
priced (the offering's `pricing`: `quote`, `fixed`, `hourly`,
|
|
133
|
-
`subscription`) in words, with no figure.
|
|
134
|
-
5. **Label.** Set `placeholder: true` in the frontmatter.
|
|
135
|
-
6. **Check** (below).
|
|
136
|
-
|
|
137
|
-
## Step 2 — Tier 2: the company's own draft
|
|
138
|
-
|
|
139
|
-
1. **Ask.** Ask each `intake[]` question from the profile, in order, one
|
|
140
|
-
at a time. Record the answers in `content/_intake.yaml`:
|
|
141
|
-
|
|
142
|
-
```yaml
|
|
143
|
-
answers:
|
|
144
|
-
offerings: [Drain clearing, Emergency call-out]
|
|
145
|
-
service_area: "…"
|
|
146
|
-
faq: null # unanswered
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
A list answer stays a list. An unanswered or skipped question is
|
|
150
|
-
`null`. Offerings are named exactly as the profile names them.
|
|
151
|
-
2. **Fill.** Fill the page as in Step 1, but from `_site.mdx` and the
|
|
152
|
-
answers first; the profile fills only what the answers leave open.
|
|
153
|
-
- A profile offering the owner does not offer appears on no page:
|
|
154
|
-
remove its `items` entries, and ask before deleting its Service
|
|
155
|
-
page (the one file this skill may delete).
|
|
156
|
-
- A price appears only when the owner gave one, with `priceCurrency`.
|
|
157
|
-
- The company name appears in each page's frontmatter (`title` or
|
|
158
|
-
`description`), and the service area in the home hero when given.
|
|
159
|
-
- A section whose sources have no answer keeps its tier-1 sample
|
|
160
|
-
content.
|
|
161
|
-
3. **Never invent** still holds for everything the owner did not give.
|
|
162
|
-
Testimonials stay `Sample customer` unless the owner supplies a
|
|
163
|
-
customer's name with permission to publish it.
|
|
164
|
-
4. **Label.** Set `placeholder: false` only when every section is filled
|
|
165
|
-
from company facts:
|
|
166
|
-
- every section type in the stack appears in the `fills` of at least
|
|
167
|
-
one intake item whose answer is not empty, and
|
|
168
|
-
- when the stack has a `hero` or `cta`, `_site.mdx` has
|
|
169
|
-
`siteIdentity.name`.
|
|
170
|
-
|
|
171
|
-
A page with no sections is judged on its body: a `Service` page is
|
|
172
|
-
cleared only when the `offerings` answer names its offering; any
|
|
173
|
-
other page with no sections keeps `placeholder: true`. In every other
|
|
174
|
-
case, keep `placeholder: true`.
|
|
175
|
-
5. **Check** (below).
|
|
176
|
-
|
|
177
|
-
## The check
|
|
178
|
-
|
|
179
|
-
Run the site's own structured-data check. It reads every content page
|
|
180
|
-
through the same frontmatter and section schemas the site renders with,
|
|
181
|
-
and validates each page's JSON-LD:
|
|
182
|
-
|
|
183
|
-
```bash
|
|
184
|
-
pnpm --filter web test -- lib/__tests__/structured-data.test.tsx
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
Then search the filled file for a `{` or `}` in any frontmatter string or
|
|
188
|
-
in the body outside `{/* … */}` comments. Fix everything either reports
|
|
189
|
-
and run both again. Stop only when both are clean.
|
|
190
|
-
|
|
191
|
-
## What this skill does not do
|
|
192
|
-
|
|
193
|
-
- Tier 3 (the Messaging Canvas through the content pipeline).
|
|
194
|
-
- Create a page — use `add-page`.
|
|
195
|
-
- Edit `_site.mdx`, the nav or the page roster. A profile's `roster` and
|
|
196
|
-
`nav` entries are declared for a later step and are not applied here,
|
|
197
|
-
and a profile page type this site cannot render (Step 0) is never
|
|
198
|
-
suggested.
|
|
199
|
-
- Write a price, a name or an address the owner did not give.
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
# Category profile — Local Plumbing (#6640; starter-template contract § D).
|
|
2
|
-
# Read by the `fill-page` skill: tier 1 fills a page with labelled sample
|
|
3
|
-
# content from this file; tier 2 asks `intake[]` and fills from the answers.
|
|
4
|
-
# Twin: templates/.claude/skills/fill-page/profiles/plumbing.yaml (byte-identical).
|
|
5
|
-
slug: plumbing
|
|
6
|
-
shape: local-business-service
|
|
7
|
-
subtype: Plumber
|
|
8
|
-
secondary: Person
|
|
9
|
-
offerings:
|
|
10
|
-
- name: Drain clearing
|
|
11
|
-
job: Clears blocked sinks, showers, toilets and main drains, and says what caused the blockage.
|
|
12
|
-
pricing: quote
|
|
13
|
-
- name: Water heater installation
|
|
14
|
-
job: Removes the old tank or boiler and fits, tests and registers a new water heater.
|
|
15
|
-
pricing: fixed
|
|
16
|
-
- name: Emergency call-out
|
|
17
|
-
job: A plumber at the door the same day for a burst pipe, a flood or no water at all.
|
|
18
|
-
pricing: hourly
|
|
19
|
-
- name: Leak detection and repair
|
|
20
|
-
job: Finds the hidden leak behind a wall or under a floor and fixes it with the least damage.
|
|
21
|
-
pricing: quote
|
|
22
|
-
proof:
|
|
23
|
-
- kind: credential
|
|
24
|
-
label: Licensed and insured
|
|
25
|
-
- kind: reviews
|
|
26
|
-
label: Reviews from local customers
|
|
27
|
-
- kind: response-time
|
|
28
|
-
label: Same-day response on emergency calls
|
|
29
|
-
- kind: guarantee
|
|
30
|
-
label: Workmanship guaranteed
|
|
31
|
-
vocabulary:
|
|
32
|
-
problem:
|
|
33
|
-
- burst pipe
|
|
34
|
-
- blocked drain
|
|
35
|
-
- no hot water
|
|
36
|
-
- leaking tap
|
|
37
|
-
- low water pressure
|
|
38
|
-
- water stain on the ceiling
|
|
39
|
-
service:
|
|
40
|
-
- drain clearing
|
|
41
|
-
- water heater install
|
|
42
|
-
- emergency call-out
|
|
43
|
-
- leak repair
|
|
44
|
-
- pipe replacement
|
|
45
|
-
outcome:
|
|
46
|
-
- same-day fix
|
|
47
|
-
- hot water back on
|
|
48
|
-
- no water damage
|
|
49
|
-
- upfront quote
|
|
50
|
-
- tidy job, nothing left behind
|
|
51
|
-
roster:
|
|
52
|
-
- op: add
|
|
53
|
-
route: /meet/emergency
|
|
54
|
-
pageType: BookCall
|
|
55
|
-
reason: Emergency jobs book a call, not a form.
|
|
56
|
-
- op: drop
|
|
57
|
-
route: /pricing
|
|
58
|
-
reason: Most plumbing work is quote-priced, so a price table misleads.
|
|
59
|
-
nav:
|
|
60
|
-
Services: Plumbing services
|
|
61
|
-
content_posture: sample
|
|
62
|
-
intake:
|
|
63
|
-
- id: offerings
|
|
64
|
-
ask: Which of these jobs do you take on, and is there one you do not list? (Drain clearing, water heater installation, emergency call-out, leak detection and repair.)
|
|
65
|
-
fills: [hero, items]
|
|
66
|
-
- id: service_area
|
|
67
|
-
ask: Which towns, postcodes or radius do you cover?
|
|
68
|
-
fills: [hero, cta]
|
|
69
|
-
- id: about
|
|
70
|
-
ask: How did the business start, who does the work, and what will you not take on?
|
|
71
|
-
fills: [content-block]
|
|
72
|
-
- id: proof
|
|
73
|
-
ask: What can a customer check — your licence, your insurance, your reviews, your response time, your guarantee?
|
|
74
|
-
fills: [stats, testimonials, social-proof]
|
|
75
|
-
- id: faq
|
|
76
|
-
ask: What do customers ask before they book, and what do you tell them?
|
|
77
|
-
fills: [faq]
|
|
78
|
-
- id: contact
|
|
79
|
-
ask: How should a customer reach you — phone, form or booking — and how fast do you reply?
|
|
80
|
-
fills: [cta]
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# Section sources
|
|
2
|
-
|
|
3
|
-
Where each section's copy comes from. **Profile** is tier 1's source;
|
|
4
|
-
**Intake** lists the `intake[]` answers that fill the section at tier 2
|
|
5
|
-
(the same ids appear in each question's `fills`). Fields not named here
|
|
6
|
-
are layout options: keep them as they are.
|
|
7
|
-
|
|
8
|
-
| Section type | Copy fields | Profile (tier 1) | Intake (tier 2) |
|
|
9
|
-
| --------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
|
|
10
|
-
| `hero` | `eyebrow`, `headline`, `subheadline`, `primaryCta.label`, `secondaryCta.label` | `vocabulary.problem` + `vocabulary.outcome` for the headline; the category name for the eyebrow | `offerings`, `service_area` |
|
|
11
|
-
| `items` | `headline`, `description`, `items[].title`, `items[].description`, `items[].link` | one item per `offerings[]` entry: `title` = `name`, `description` = `job`, `link.href` = `/services/<kebab name>` | `offerings` |
|
|
12
|
-
| `cta` | `headline`, `description`, `primaryCta.label`, `secondaryCta.label` | `vocabulary.outcome` | `service_area`, `contact` |
|
|
13
|
-
| `content-block` | `eyebrow`, `headline`, `body` | `proof[].label` and `vocabulary.service`, with no names, places or numbers | `about` |
|
|
14
|
-
| `faq` | `headline`, `items[].question`, `items[].answer` | questions in `vocabulary.problem` words; answers from `offerings[].pricing` and `proof[]` | `faq` |
|
|
15
|
-
| `stats` | `headline`, `items[].label`, `items[].value` | `proof[].label` as the label; a value only when it is not a measurement (e.g. `Same day`) | `proof` |
|
|
16
|
-
| `testimonials` | `headline`, `items[].quote`, `items[].author` | quotes in `vocabulary.outcome` words; `author: Sample customer`; no `company`, `rating` or `avatar` | `proof` |
|
|
17
|
-
| `social-proof` | `headline`, labels | `proof[].label` | `proof` |
|
|
18
|
-
|
|
19
|
-
A section type not in this table: fill its copy fields from the profile's
|
|
20
|
-
`vocabulary` and leave its data otherwise as it was. At tier 2 no intake
|
|
21
|
-
answer fills it, so a page carrying it keeps `placeholder: true`.
|
|
22
|
-
|
|
23
|
-
## Frontmatter
|
|
24
|
-
|
|
25
|
-
| Key | Tier 1 | Tier 2 |
|
|
26
|
-
| ---------------------- | -------------------------------------------------- | ------------------------------------------- |
|
|
27
|
-
| `title`, `description` | the category's words; the company name from `_site.mdx` where the page names the company | the company name and the owner's answers |
|
|
28
|
-
| `provider.name` | `siteIdentity.name` | `siteIdentity.name` |
|
|
29
|
-
| `schema.serviceType` | the offering `name` | the offering `name` |
|
|
30
|
-
| `schema.areaServed` | removed | the `service_area` answer |
|
|
31
|
-
| `price`, `priceCurrency`, `availability`, `stripe` | removed | only a price the owner gave, with its currency |
|
|
32
|
-
| `placeholder` | `true` | by the rule in `SKILL.md` Step 2.4 |
|
|
33
|
-
|
|
34
|
-
## Pricing words (Service bodies, FAQ answers)
|
|
35
|
-
|
|
36
|
-
| `pricing` | Say |
|
|
37
|
-
| -------------- | ------------------------------------------------------------------- |
|
|
38
|
-
| `quote` | priced after a look at the job; the quote is given before work starts |
|
|
39
|
-
| `fixed` | one fixed price for the job, agreed before work starts |
|
|
40
|
-
| `hourly` | charged by the hour on site, with the call-out rate stated up front |
|
|
41
|
-
| `subscription` | a regular plan, billed on a schedule |
|