create-ailk 0.6.1 → 0.8.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 +87 -14
- package/component-catalog.json +735 -95
- package/component-catalog.md +13 -7
- package/dist/add-module-core.d.ts +22 -0
- package/dist/add-module-core.js +50 -0
- package/dist/add-module.d.ts +89 -0
- package/dist/add-module.js +334 -0
- package/dist/category-profile.d.ts +126 -0
- package/dist/category-profile.js +262 -0
- package/dist/cli.js +89 -29
- package/dist/index.d.ts +4 -2
- package/dist/index.js +6 -1
- package/dist/lib/extract-module-bundle.d.ts +56 -1
- package/dist/lib/extract-module-bundle.js +170 -14
- package/dist/lib/license-key-files.d.ts +15 -0
- package/dist/lib/license-key-files.js +102 -0
- package/dist/lib/module-license-gate.d.ts +79 -0
- package/dist/lib/module-license-gate.js +160 -12
- package/dist/lib/module-tier.d.ts +45 -16
- package/dist/lib/module-tier.js +76 -0
- package/dist/lib/pro-callout.d.ts +16 -10
- package/dist/lib/pro-callout.js +34 -3
- package/dist/license-set.d.ts +34 -0
- package/dist/license-set.js +194 -0
- package/dist/module-architecture.d.ts +20 -0
- package/dist/module-architecture.js +77 -19
- package/dist/modules/commerce/0.6.1.tar.gz +0 -0
- package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
- package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
- package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
- package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
- package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
- package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
- package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
- package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
- package/dist/modules/workspace/0.6.1.tar.gz +0 -0
- package/dist/parse-args.d.ts +85 -10
- package/dist/parse-args.js +227 -15
- package/dist/programmatic.d.ts +81 -5
- package/dist/programmatic.js +291 -32
- package/dist/site-chrome.d.ts +18 -7
- package/dist/site-chrome.js +32 -14
- package/dist/site-roster.d.ts +127 -0
- package/dist/site-roster.js +499 -0
- package/dist/starter-grid.d.ts +216 -0
- package/dist/starter-grid.js +449 -0
- package/dist/surfaces.d.ts +20 -7
- package/dist/surfaces.js +204 -24
- package/dist/sync-routes.d.ts +9 -0
- package/dist/sync-routes.js +2 -2
- package/dist/template-manifest.d.ts +94 -19
- package/dist/template-manifest.js +170 -289
- package/dist/template-names.d.ts +14 -12
- package/dist/template-names.js +13 -17
- package/package.json +6 -4
- package/site-templates/_base/content/en/BookCall/book-call.mdx +52 -0
- package/site-templates/_base/content/en/Feature/feature-1.mdx +69 -0
- package/site-templates/_base/content/en/Feature/feature-2.mdx +69 -0
- package/site-templates/_base/content/en/Feature/feature-3.mdx +69 -0
- package/site-templates/_base/content/en/FeaturesList/features.mdx +91 -0
- package/site-templates/_base/content/en/GlossaryList/glossary.mdx +65 -0
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +67 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-four.mdx +105 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-one.mdx +34 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-three.mdx +94 -0
- package/site-templates/_base/content/en/PortfolioItem/sample-two.mdx +45 -0
- package/site-templates/_base/content/en/PortfolioList/portfolio.mdx +169 -0
- package/site-templates/_base/content/en/Pricing/pricing.mdx +71 -62
- package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +17 -3
- package/site-templates/_base/content/en/TermsPage/terms.mdx +2 -3
- package/site-templates/_base/content/en/UseCase/persona-1.mdx +69 -0
- package/site-templates/_base/content/en/UseCase/persona-2.mdx +69 -0
- package/site-templates/_base/content/en/UseCase/persona-3.mdx +69 -0
- package/site-templates/designs/minimalism/DESIGN.md +162 -0
- package/site-templates/designs/standard/DESIGN.md +167 -0
- package/site-templates/marketing-site/DESIGN.md +59 -40
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +376 -57
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +230 -90
- package/site-templates/marketing-site/ailk-template.json +1 -0
- package/site-templates/marketing-site-free/DESIGN.md +59 -40
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +323 -63
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +323 -113
- package/site-templates/marketing-site-free/ailk-template.json +3 -2
- package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +199 -0
- package/site-templates/minimalism-site/DESIGN.md +54 -40
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +246 -19
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +173 -51
- package/site-templates/minimalism-site/ailk-template.json +3 -2
- package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +52 -0
- package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +3 -37
- package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- package/site-templates/saas-product/DESIGN.md +59 -40
- package/site-templates/saas-product/TEMPLATE-MANIFEST.md +9 -1
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +369 -0
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +302 -0
- package/site-templates/shapes/local-business-service/ailk-template.json +10 -0
- package/site-templates/{simple-service → shapes/local-business-service}/content/en/AboutPage/about.mdx +4 -4
- package/site-templates/shapes/local-business-service/content/en/HomePage/home.mdx +70 -0
- package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/shapes/local-business-service/content/en/PortfolioList/portfolio.mdx +171 -0
- package/site-templates/shapes/local-business-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- package/site-templates/shapes/local-business-service/content/en/Service/first-job.mdx +39 -0
- package/site-templates/shapes/local-business-service/content/en/Service/second-job.mdx +39 -0
- package/site-templates/shapes/local-business-service/content/en/Service/third-job.mdx +39 -0
- package/site-templates/shapes/local-business-service/content/en/ServicesList/services.mdx +61 -0
- package/site-templates/shapes/local-business-service/provenance.json +4 -0
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +362 -0
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +300 -0
- package/site-templates/shapes/organization-service/ailk-template.json +10 -0
- package/site-templates/shapes/organization-service/content/en/AboutPage/about.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/HomePage/home.mdx +85 -0
- package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/shapes/organization-service/content/en/PortfolioList/portfolio.mdx +171 -0
- package/site-templates/shapes/organization-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- package/site-templates/shapes/organization-service/content/en/Service/first-engagement.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/Service/second-engagement.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/Service/third-engagement.mdx +38 -0
- package/site-templates/shapes/organization-service/content/en/ServicesList/services.mdx +72 -0
- package/site-templates/shapes/organization-service/provenance.json +4 -0
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +369 -0
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +299 -0
- package/site-templates/shapes/person-service/ailk-template.json +10 -0
- package/site-templates/shapes/person-service/content/_site.mdx +85 -0
- package/site-templates/shapes/person-service/content/en/AboutPage/about.mdx +39 -0
- package/site-templates/shapes/person-service/content/en/HomePage/home.mdx +97 -0
- package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
- package/site-templates/shapes/person-service/content/en/PortfolioList/portfolio.mdx +169 -0
- package/site-templates/shapes/person-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
- package/site-templates/shapes/person-service/content/en/Service/first-engagement.mdx +38 -0
- package/site-templates/shapes/person-service/content/en/Service/second-engagement.mdx +38 -0
- package/site-templates/shapes/person-service/content/en/Service/third-engagement.mdx +38 -0
- package/site-templates/{simple-service → shapes/person-service}/content/en/ServicesList/services.mdx +16 -16
- package/site-templates/shapes/person-service/provenance.json +4 -0
- package/templates/.claude/settings.json +15 -0
- package/templates/.claude/skills/README.md +10 -9
- package/templates/.claude/skills/{scaffold-auth → add-auth}/SKILL.md +7 -7
- package/templates/.claude/skills/{scaffold-auth → add-auth}/references/env-vars.md +2 -2
- package/templates/.claude/skills/{scaffold-auth → add-auth}/references/oauth-app-setup.md +3 -3
- package/templates/.claude/skills/{scaffold-page → add-page}/SKILL.md +76 -62
- package/templates/.claude/skills/add-page/references/page-types.md +115 -0
- package/templates/.claude/skills/{scaffold-page → add-page}/templates/content.mdx.tmpl +4 -4
- package/templates/.claude/skills/{scaffold-page → add-page}/templates/route.tsx.tmpl +1 -1
- package/templates/.claude/skills/{audit-site → check-site}/SKILL.md +16 -17
- package/templates/.claude/skills/{audit-site → check-site}/references/common-errors.md +3 -3
- package/templates/.claude/skills/{audit-site → check-site}/references/findings-glossary.md +16 -14
- package/templates/.claude/skills/{audit-site → check-site}/references/severity-levels.md +1 -1
- package/templates/.claude/skills/fill-page/SKILL.md +180 -0
- package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +80 -0
- package/templates/.claude/skills/fill-page/references/section-sources.md +41 -0
- package/templates/.claude/skills/setup-database/SKILL.md +156 -0
- package/templates/.claude/skills/setup-database/scripts/verify-inbox.ts +154 -0
- package/templates/.claude/skills/{provision-config → setup-deploy}/SKILL.md +73 -41
- package/templates/.claude/skills/{provision-config → setup-deploy}/references/env-vars-template.md +21 -30
- package/templates/.claude/skills/setup-deploy/references/runbook-template.md +204 -0
- package/templates/.claude/skills/{provision-config → setup-deploy}/references/vercel-json-template.md +1 -1
- package/templates/.claude/skills/upgrade-plan/SKILL.md +117 -0
- package/templates/.claude/skills/upgrade-plan/references/options.md +24 -0
- package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +527 -0
- package/templates/.github/workflows/ci.yml +5 -0
- package/templates/CLAUDE.md +8 -2
- package/templates/DESIGN.md +56 -44
- package/templates/README.md +116 -46
- package/templates/apps/api/.env.example +2 -1
- package/templates/apps/api/CLAUDE.md +3 -2
- package/templates/apps/api/package.json +1 -1
- package/templates/apps/api/src/config/__tests__/index.test.ts +146 -10
- package/templates/apps/api/src/config/index.ts +46 -14
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +30 -0
- package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +96 -0
- package/templates/apps/api/src/lib/lead-capture-store.ts +12 -0
- package/templates/apps/api/src/lib/lead-delivery.ts +52 -0
- package/templates/apps/api/src/lib/license-key-store.ts +45 -0
- package/templates/apps/api/src/lib/site.ts +5 -3
- package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +97 -0
- package/templates/apps/api/src/middleware/auth.ts +9 -0
- package/templates/apps/api/src/openapi/spec.ts +63 -12
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +127 -5
- package/templates/apps/api/src/routes/content/get-page.ts +86 -2
- package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +88 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +100 -0
- package/templates/apps/api/src/routes/leads/index.ts +46 -1
- package/templates/apps/api/src/server.ts +13 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +194 -0
- package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +124 -0
- package/templates/apps/api/src/services/lead-routing.ts +140 -30
- package/templates/apps/api/src/services/leads-inbox.ts +48 -11
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +75 -1
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +57 -9
- package/templates/apps/mcp/__tests__/parity.test.ts +5 -4
- package/templates/apps/mcp/package.json +2 -1
- package/templates/apps/mcp/src/index.ts +7 -0
- package/templates/apps/mcp/src/lib/audit-single-page.ts +36 -10
- package/templates/apps/mcp/src/lib/license-key-store.ts +45 -0
- package/templates/apps/mcp/src/tools/create_project.ts +21 -6
- package/templates/apps/web/__tests__/setup.ts +18 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +10 -2
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +2 -2
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +10 -4
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +22 -14
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +26 -18
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +12 -4
- package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +35 -4
- package/templates/apps/web/app/[locale]/[slug]/page.tsx +49 -9
- package/templates/apps/web/app/[locale]/book-a-call/page.tsx +50 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +67 -29
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +94 -11
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-form-text.test.ts +32 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +129 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +228 -86
- package/templates/apps/web/app/[locale]/contact/contact-form-text.ts +50 -0
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +73 -20
- package/templates/apps/web/app/[locale]/contact/page.tsx +115 -7
- package/templates/apps/web/app/[locale]/contact/site-contact-email.ts +54 -0
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/features/[slug]/page.tsx +64 -0
- package/templates/apps/web/app/[locale]/features/page.tsx +52 -0
- package/templates/apps/web/app/[locale]/glossary/page.tsx +50 -0
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/how-to/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/layout.tsx +146 -52
- package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +75 -0
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +163 -0
- package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +66 -0
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +142 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +59 -0
- package/templates/apps/web/app/[locale]/portfolio/page.tsx +49 -0
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/{cancellation → purchase-policy}/page.tsx +15 -10
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +14 -1
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/software/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/tech-articles/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +63 -0
- package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/use-cases/page.tsx +15 -2
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/whitepapers/page.tsx +25 -15
- package/templates/apps/web/app/error-fallback.tsx +17 -45
- package/templates/apps/web/app/not-found.tsx +14 -38
- package/templates/apps/web/app/og/route.tsx +6 -0
- package/templates/apps/web/eslint.config.mjs +3 -0
- package/templates/apps/web/instrumentation.ts +14 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +38 -0
- package/templates/apps/web/lib/license-key-store.ts +52 -0
- package/templates/apps/web/lib/nav-config.ts +82 -18
- package/templates/apps/web/lib/prisma-for-auth.ts +9 -6
- package/templates/apps/web/lib/site-brand.tsx +1 -1
- package/templates/apps/web/lib/site-chrome.ts +3 -3
- package/templates/apps/web/lib/site-identity.ts +148 -0
- package/templates/apps/web/lib/site-theme.ts +11 -0
- package/templates/apps/web/messages/en.json +78 -37
- package/templates/apps/web/public/android-chrome-192x192.png +0 -0
- package/templates/apps/web/public/android-chrome-512x512.png +0 -0
- package/templates/apps/web/public/apple-touch-icon.png +0 -0
- package/templates/apps/web/public/favicon-16x16.png +0 -0
- package/templates/apps/web/public/favicon-32x32.png +0 -0
- package/templates/apps/web/public/favicon.ico +0 -0
- package/templates/apps/web/public/favicon.svg +3 -3
- package/templates/apps/web/public/logomark.svg +6 -3
- package/templates/apps/web/public/logos/ai-launch-kit-light.svg +4 -0
- package/templates/apps/web/public/logos/ai-launch-kit.svg +4 -0
- package/templates/apps/web/public/logos/atomic-agent-light.svg +4 -0
- package/templates/apps/web/public/logos/atomic-agent.svg +4 -0
- package/templates/apps/web/public/logos/salessmyth-light.svg +4 -0
- package/templates/apps/web/public/logos/salessmyth.svg +4 -0
- package/templates/apps/web/public/site.webmanifest +4 -4
- package/templates/apps/web/public/wordmark-light.svg +4 -0
- package/templates/apps/web/public/wordmark.svg +3 -3
- package/templates/apps/web/security-headers.config.ts +5 -1
- package/templates/content/_site.mdx +25 -0
- package/templates/content/en/AboutPage/about.mdx +150 -144
- package/templates/content/en/BlogList/blog.mdx +126 -0
- package/templates/content/en/ContactPage/contact.mdx +21 -92
- package/templates/content/en/FAQPage/faq.mdx +86 -57
- package/templates/content/en/HomePage/home.mdx +268 -217
- package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +181 -0
- package/templates/content/en/UseCasesList/use-cases.mdx +170 -0
- package/templates/database/CHANGELOG.md +14 -0
- package/templates/database/package.json +1 -1
- package/templates/package.json +3 -1
- package/templates/tools/lint-architecture.ts +315 -0
- package/templates/tools/tsconfig.json +14 -0
- package/versions.json +33 -33
- package/site-templates/simple-service/DESIGN.md +0 -148
- package/site-templates/simple-service/TEMPLATE-MANIFEST.json +0 -240
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +0 -278
- package/site-templates/simple-service/ailk-template.json +0 -10
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +0 -67
- package/site-templates/simple-service/content/en/FAQPage/faq.mdx +0 -56
- package/site-templates/simple-service/content/en/HomePage/home.mdx +0 -85
- package/site-templates/simple-service/content/en/Service/landscape-design.mdx +0 -40
- package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +0 -40
- package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +0 -40
- package/site-templates/simple-service/provenance.json +0 -4
- package/templates/.claude/skills/add-schema/SKILL.md +0 -277
- package/templates/.claude/skills/add-schema/references/conflict-handling.md +0 -200
- package/templates/.claude/skills/add-schema/references/file-finding.md +0 -123
- package/templates/.claude/skills/add-schema/references/schema-types.md +0 -234
- package/templates/.claude/skills/provision-config/references/runbook-template.md +0 -248
- package/templates/.claude/skills/scaffold-page/references/page-types.md +0 -169
- package/templates/.claude/skills/suggest-site-pages/SKILL.md +0 -242
- package/templates/.claude/skills/suggest-site-pages/references/categories.md +0 -287
- package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +0 -150
- package/templates/apps/web/app/[locale]/returns/page.tsx +0 -44
- package/templates/apps/web/app/[locale]/shipping/page.tsx +0 -44
- package/templates/content/en/CancellationPolicyPage/cancellation.mdx +0 -108
- package/templates/content/en/LandingPage/blog.mdx +0 -83
- package/templates/content/en/LandingPage/features.mdx +0 -103
- package/templates/content/en/LandingPage/how-it-works.mdx +0 -143
- package/templates/content/en/ReturnsPolicyPage/returns.mdx +0 -108
- package/templates/content/en/ShippingPolicyPage/shipping.mdx +0 -108
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
* @file lead-routing.ts
|
|
3
3
|
* @description Cross-namespace lead orchestration service (ADR 0009 D3).
|
|
4
4
|
*
|
|
5
|
-
* Pipeline: validate → enrich → persist (DB with file
|
|
5
|
+
* Pipeline: validate → delivery guard → enrich → persist (DB with file
|
|
6
|
+
* fallback) → email. Where a lead may go is `resolveLeadDelivery()`
|
|
7
|
+
* (`lib/lead-delivery.ts`, #6730 D1): with no database but the email vars set,
|
|
8
|
+
* the owner email IS the delivery; in production with neither, nothing is
|
|
9
|
+
* accepted. A lead is reported sent only for a durable row or an email the
|
|
10
|
+
* provider accepted — never for the non-durable file fallback in production.
|
|
6
11
|
* This is the first implementation of ADR 0009 D3's service-layer pattern.
|
|
7
12
|
* Future cross-namespace orchestrators in apps/api/src/services/ should mirror
|
|
8
13
|
* this shape: type-result returns, mocked-dep tests, non-transactional secondary
|
|
@@ -32,6 +37,7 @@ import { leadInputSchema } from "@working-theory/validation";
|
|
|
32
37
|
|
|
33
38
|
import { writeLeadFallback } from "../lib/file-fallback.js";
|
|
34
39
|
import { appendLeadRecord } from "../lib/lead-capture-store.js";
|
|
40
|
+
import { acceptsKind, resolveLeadDelivery } from "../lib/lead-delivery.js";
|
|
35
41
|
import { prisma } from "../lib/prisma.js";
|
|
36
42
|
|
|
37
43
|
import { enrichLead } from "./enrichment-shim.js";
|
|
@@ -65,11 +71,14 @@ export type SourceMeta = {
|
|
|
65
71
|
receivedAt: string; // ISO
|
|
66
72
|
};
|
|
67
73
|
|
|
74
|
+
/** Where the lead landed: a database row, the local file fallback, or the owner's inbox by email (#6730). */
|
|
75
|
+
export type Persisted = "db" | "file" | "email";
|
|
76
|
+
|
|
68
77
|
export type RouteLeadResult =
|
|
69
78
|
| {
|
|
70
79
|
ok: true;
|
|
71
80
|
id: string;
|
|
72
|
-
persisted:
|
|
81
|
+
persisted: Persisted;
|
|
73
82
|
email:
|
|
74
83
|
| { ok: true; messageId: string }
|
|
75
84
|
| { ok: false; error: string }
|
|
@@ -78,7 +87,11 @@ export type RouteLeadResult =
|
|
|
78
87
|
| {
|
|
79
88
|
ok: false;
|
|
80
89
|
error: {
|
|
81
|
-
kind:
|
|
90
|
+
kind:
|
|
91
|
+
| "invalid_input"
|
|
92
|
+
| "persist_failed"
|
|
93
|
+
| "config_missing"
|
|
94
|
+
| "delivery_failed";
|
|
82
95
|
message: string;
|
|
83
96
|
};
|
|
84
97
|
};
|
|
@@ -272,7 +285,7 @@ type EmailResult =
|
|
|
272
285
|
async function sendEmail(
|
|
273
286
|
input: RouteLeadInput,
|
|
274
287
|
id: string,
|
|
275
|
-
persisted:
|
|
288
|
+
persisted: Persisted,
|
|
276
289
|
): Promise<EmailResult> {
|
|
277
290
|
// Request-and-confirm SUPERSEDES this notification for a callback (#5970
|
|
278
291
|
// D6). AC-1 requires the tenant to receive exactly one email, and the
|
|
@@ -316,7 +329,22 @@ async function sendEmail(
|
|
|
316
329
|
siteId: input.siteId,
|
|
317
330
|
source: input.kind === "callback" ? "callback" : input.source,
|
|
318
331
|
email: input.kind === "callback" ? "" : input.email,
|
|
319
|
-
|
|
332
|
+
// A callback's contact details are top-level fields, not payload keys
|
|
333
|
+
// (#6730): with no database the email is the only record of the
|
|
334
|
+
// request, so it must carry the name and number to call back.
|
|
335
|
+
payload:
|
|
336
|
+
input.kind === "subscribe"
|
|
337
|
+
? {}
|
|
338
|
+
: input.kind === "callback"
|
|
339
|
+
? {
|
|
340
|
+
...input.payload,
|
|
341
|
+
name: input.name,
|
|
342
|
+
phone: input.phone,
|
|
343
|
+
...(input.preferredTime !== undefined && {
|
|
344
|
+
preferredTime: input.preferredTime,
|
|
345
|
+
}),
|
|
346
|
+
}
|
|
347
|
+
: input.payload,
|
|
320
348
|
createdAt: new Date().toISOString(),
|
|
321
349
|
};
|
|
322
350
|
|
|
@@ -324,6 +352,9 @@ async function sendEmail(
|
|
|
324
352
|
lead: leadForEmail,
|
|
325
353
|
recipientEmail: to,
|
|
326
354
|
fromAddress: from,
|
|
355
|
+
// A callback has no visitor address to reply to; an empty Reply-To is
|
|
356
|
+
// not a valid header, so reply to the owner instead.
|
|
357
|
+
...(input.kind === "callback" && { replyToOverride: to }),
|
|
327
358
|
});
|
|
328
359
|
|
|
329
360
|
if (emailResult.ok) {
|
|
@@ -337,6 +368,47 @@ async function sendEmail(
|
|
|
337
368
|
return { ok: false, error: `${kind}: ${message}` };
|
|
338
369
|
}
|
|
339
370
|
|
|
371
|
+
// ─── Email delivery (#6730 D2) ────────────────────────────────────────────────
|
|
372
|
+
|
|
373
|
+
type EmailDelivery =
|
|
374
|
+
| { ok: true; id: string; email: { ok: true; messageId: string } }
|
|
375
|
+
| { ok: false; message: string };
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Deliver a lead as the owner email alone — success only when the provider
|
|
379
|
+
* accepted it. The id matches the file-fallback id format
|
|
380
|
+
* (`/^[a-z0-9_-]{8,64}$/i`).
|
|
381
|
+
*/
|
|
382
|
+
async function deliverByEmail(input: RouteLeadInput): Promise<EmailDelivery> {
|
|
383
|
+
const id = `email-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
384
|
+
const result = await sendEmail(input, id, "email");
|
|
385
|
+
if ("ok" in result && result.ok) return { ok: true, id, email: result };
|
|
386
|
+
return {
|
|
387
|
+
ok: false,
|
|
388
|
+
message: "error" in result ? result.error : `email skipped: ${result.reason}`,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ─── Lead-capture record ─────────────────────────────────────────────────────
|
|
393
|
+
|
|
394
|
+
function captureRecordFor(input: RouteLeadInput, id: string, sourceMeta: SourceMeta) {
|
|
395
|
+
return {
|
|
396
|
+
id,
|
|
397
|
+
createdAt: sourceMeta.receivedAt,
|
|
398
|
+
source: input.kind === "callback" ? "callback" : input.source,
|
|
399
|
+
data:
|
|
400
|
+
input.kind === "callback"
|
|
401
|
+
? { name: input.name, phone: input.phone, ...(input.payload ?? {}) }
|
|
402
|
+
: input.kind === "subscribe"
|
|
403
|
+
? { email: input.email }
|
|
404
|
+
: { email: input.email, ...(input.payload ?? {}) },
|
|
405
|
+
metadata: {
|
|
406
|
+
ip: sourceMeta.ip,
|
|
407
|
+
userAgent: sourceMeta.userAgent,
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
340
412
|
// ─── Main export ──────────────────────────────────────────────────────────────
|
|
341
413
|
|
|
342
414
|
export async function routeLead(
|
|
@@ -352,13 +424,13 @@ export async function routeLead(
|
|
|
352
424
|
};
|
|
353
425
|
}
|
|
354
426
|
|
|
355
|
-
// 2. Guard:
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
) {
|
|
427
|
+
// 2. Guard: where can this lead go (#6730 D1)? A kind the configured
|
|
428
|
+
// delivery cannot take — anything in production with neither a database
|
|
429
|
+
// nor the email vars, a newsletter signup with no database — is refused
|
|
430
|
+
// before any write, so no visitor is told a message was sent when it
|
|
431
|
+
// reached nobody.
|
|
432
|
+
const delivery = resolveLeadDelivery();
|
|
433
|
+
if (!acceptsKind(delivery, input.kind)) {
|
|
362
434
|
return {
|
|
363
435
|
ok: false,
|
|
364
436
|
error: {
|
|
@@ -374,9 +446,28 @@ export async function routeLead(
|
|
|
374
446
|
// the real enrichment call with proper type alignment.
|
|
375
447
|
await enrichLead(input as unknown as Parameters<typeof enrichLead>[0]);
|
|
376
448
|
|
|
449
|
+
// Email delivery — no database: the owner email is the delivery, so skip
|
|
450
|
+
// the DB and the (non-durable) file fallback entirely.
|
|
451
|
+
if (delivery === "email") {
|
|
452
|
+
const emailed = await deliverByEmail(input);
|
|
453
|
+
if (!emailed.ok) {
|
|
454
|
+
return {
|
|
455
|
+
ok: false,
|
|
456
|
+
error: { kind: "delivery_failed", message: emailed.message },
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
await appendLeadRecord(captureRecordFor(input, emailed.id, sourceMeta));
|
|
460
|
+
return {
|
|
461
|
+
ok: true,
|
|
462
|
+
id: emailed.id,
|
|
463
|
+
persisted: "email",
|
|
464
|
+
email: emailed.email,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
377
468
|
// 4. Persist primary (DB), fallback to file on error
|
|
378
469
|
let id: string;
|
|
379
|
-
let persisted:
|
|
470
|
+
let persisted: Persisted;
|
|
380
471
|
let skipEmail = false;
|
|
381
472
|
let skipReason: string | undefined;
|
|
382
473
|
|
|
@@ -424,7 +515,41 @@ export async function routeLead(
|
|
|
424
515
|
};
|
|
425
516
|
}
|
|
426
517
|
|
|
427
|
-
//
|
|
518
|
+
// In production the file fallback is not durable (a serverless instance
|
|
519
|
+
// does not outlive the request), so reporting it as success would lose
|
|
520
|
+
// the lead silently. Deliver by email when that is configured; otherwise
|
|
521
|
+
// fail loudly (#6730 D2). A newsletter signup is never emailed — one
|
|
522
|
+
// owner email per address is not a list (`acceptsKind`).
|
|
523
|
+
if (process.env.NODE_ENV === "production") {
|
|
524
|
+
const dbMsg = dbErr instanceof Error ? dbErr.message : String(dbErr);
|
|
525
|
+
if (input.kind === "subscribe") {
|
|
526
|
+
return {
|
|
527
|
+
ok: false,
|
|
528
|
+
error: { kind: "persist_failed", message: `db: ${dbMsg}` },
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
const emailed = await deliverByEmail(input);
|
|
532
|
+
if (!emailed.ok) {
|
|
533
|
+
return {
|
|
534
|
+
ok: false,
|
|
535
|
+
error: {
|
|
536
|
+
kind: "persist_failed",
|
|
537
|
+
// Not the provider's text: it can echo an address, and this
|
|
538
|
+
// message is logged (spec D5).
|
|
539
|
+
message: `db: ${dbMsg}; email delivery failed`,
|
|
540
|
+
},
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
await appendLeadRecord(captureRecordFor(input, emailed.id, sourceMeta));
|
|
544
|
+
return {
|
|
545
|
+
ok: true,
|
|
546
|
+
id: emailed.id,
|
|
547
|
+
persisted: "email",
|
|
548
|
+
email: emailed.email,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// 5. File fallback (outside production only)
|
|
428
553
|
// Include a UUID suffix so concurrent fallbacks produce unique IDs even
|
|
429
554
|
// within the same millisecond. Format still matches /^[a-z0-9_-]{8,64}$/i.
|
|
430
555
|
const fallbackLead = {
|
|
@@ -468,22 +593,7 @@ export async function routeLead(
|
|
|
468
593
|
// 6. Write to lead-capture JSONL (always-on canonical AX surface).
|
|
469
594
|
// Best-effort — a write failure does NOT roll back persistence or block email.
|
|
470
595
|
// The file is the agent-readable surface; DB is the transactional record.
|
|
471
|
-
|
|
472
|
-
id: id!,
|
|
473
|
-
createdAt: sourceMeta.receivedAt,
|
|
474
|
-
source: input.kind === "callback" ? "callback" : input.source,
|
|
475
|
-
data:
|
|
476
|
-
input.kind === "callback"
|
|
477
|
-
? { name: input.name, phone: input.phone, ...(input.payload ?? {}) }
|
|
478
|
-
: input.kind === "subscribe"
|
|
479
|
-
? { email: input.email }
|
|
480
|
-
: { email: input.email, ...(input.payload ?? {}) },
|
|
481
|
-
metadata: {
|
|
482
|
-
ip: sourceMeta.ip,
|
|
483
|
-
userAgent: sourceMeta.userAgent,
|
|
484
|
-
},
|
|
485
|
-
};
|
|
486
|
-
await appendLeadRecord(captureRecord);
|
|
596
|
+
await appendLeadRecord(captureRecordFor(input, id!, sourceMeta));
|
|
487
597
|
|
|
488
598
|
// 7. Email notification (best-effort — never rolls back persistence)
|
|
489
599
|
let emailResult: EmailResult;
|
|
@@ -25,10 +25,14 @@
|
|
|
25
25
|
*
|
|
26
26
|
* Two preconditions, neither of which this file can assert:
|
|
27
27
|
*
|
|
28
|
-
* 1.
|
|
29
|
-
* `multi-tenant` module
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* 1. The caller's scope must resolve to a workspace. With the
|
|
29
|
+
* `multi-tenant` module that is `resolveOwnerWorkspaces`. Without it
|
|
30
|
+
* (#6730 D7) — Free and Pro — that resolver yields none by design (its
|
|
31
|
+
* own header, #5677), so the scope is instead the default workspace,
|
|
32
|
+
* granted ONLY through `resolveDefaultTenantForOperator`
|
|
33
|
+
* (`default-tenant.ts`): `AILK_OPERATOR_EMAIL` set, the caller's
|
|
34
|
+
* VERIFIED email equal to it, and the deployment unambiguously
|
|
35
|
+
* one-workspace. Any other caller gets the empty page, never a wider one.
|
|
32
36
|
* 2. The `siteId` the leads were WRITTEN under must equal a workspace id,
|
|
33
37
|
* and there are TWO independent sources of it — both must agree with the
|
|
34
38
|
* workspace, and neither is `currentSiteId()` on the primary path:
|
|
@@ -67,8 +71,10 @@
|
|
|
67
71
|
import type { Prisma } from "@working-theory/database";
|
|
68
72
|
import type { Id } from "@working-theory/ids";
|
|
69
73
|
|
|
74
|
+
import { isModuleEnabled } from "../config/modules.js";
|
|
70
75
|
import { prisma } from "../lib/prisma.js";
|
|
71
76
|
|
|
77
|
+
import { resolveDefaultTenantForOperator } from "./default-tenant.js";
|
|
72
78
|
import { resolveOwnerWorkspaces } from "./owner-workspaces.js";
|
|
73
79
|
|
|
74
80
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
@@ -178,6 +184,40 @@ export function readName(payload: Prisma.JsonValue): string | null {
|
|
|
178
184
|
return typeof name === "string" ? name.slice(0, MAX_NAME_LENGTH) : null;
|
|
179
185
|
}
|
|
180
186
|
|
|
187
|
+
// ─── Scope ────────────────────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The workspace ids whose leads the caller may read. With `multi-tenant`,
|
|
191
|
+
* every workspace they own or administer (`resolveOwnerWorkspaces`,
|
|
192
|
+
* unchanged). Without it (#6730 D7), the default workspace — only for the
|
|
193
|
+
* pinned, verified operator on a one-workspace deployment; `null` from the
|
|
194
|
+
* resolver means no scope at all.
|
|
195
|
+
*/
|
|
196
|
+
async function resolveInboxSiteIds(
|
|
197
|
+
userId: Id<"user">,
|
|
198
|
+
): Promise<{ ok: true; siteIds: string[] } | { ok: false; message: string }> {
|
|
199
|
+
if (!isModuleEnabled("multi-tenant")) {
|
|
200
|
+
try {
|
|
201
|
+
const tenant = await resolveDefaultTenantForOperator(userId);
|
|
202
|
+
return {
|
|
203
|
+
ok: true,
|
|
204
|
+
siteIds: tenant && LEAD_INBOX_ROLES.has(tenant.role) ? [tenant.workspaceId] : [],
|
|
205
|
+
};
|
|
206
|
+
} catch (err) {
|
|
207
|
+
return { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const workspaces = await resolveOwnerWorkspaces(userId);
|
|
212
|
+
if (!workspaces.ok) return { ok: false, message: workspaces.error.message };
|
|
213
|
+
return {
|
|
214
|
+
ok: true,
|
|
215
|
+
siteIds: workspaces.value
|
|
216
|
+
.filter((workspace) => LEAD_INBOX_ROLES.has(workspace.role))
|
|
217
|
+
.map((workspace) => workspace.id),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
181
221
|
// ─── Main export ──────────────────────────────────────────────────────────────
|
|
182
222
|
|
|
183
223
|
/**
|
|
@@ -199,17 +239,14 @@ export async function listOwnedLeads(params: {
|
|
|
199
239
|
cursor?: string | undefined;
|
|
200
240
|
limit: number;
|
|
201
241
|
}): Promise<ListLeadsResult> {
|
|
202
|
-
const
|
|
203
|
-
if (!
|
|
242
|
+
const scope = await resolveInboxSiteIds(params.userId);
|
|
243
|
+
if (!scope.ok) {
|
|
204
244
|
return {
|
|
205
245
|
ok: false,
|
|
206
|
-
error: { kind: "internal_error", message:
|
|
246
|
+
error: { kind: "internal_error", message: scope.message },
|
|
207
247
|
};
|
|
208
248
|
}
|
|
209
|
-
|
|
210
|
-
const siteIds = workspaces.value
|
|
211
|
-
.filter((workspace) => LEAD_INBOX_ROLES.has(workspace.role))
|
|
212
|
-
.map((workspace) => workspace.id);
|
|
249
|
+
const siteIds = scope.siteIds;
|
|
213
250
|
|
|
214
251
|
// No workspace the caller owns or administers means no lead can match, so
|
|
215
252
|
// there is nothing to query. An empty page is the honest answer — the same
|
|
@@ -15,9 +15,17 @@
|
|
|
15
15
|
* - No fetch() call (structural test)
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { readFileSync } from "node:fs";
|
|
18
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
19
20
|
import { resolve, join } from "node:path";
|
|
20
21
|
|
|
22
|
+
import {
|
|
23
|
+
entityMarkup,
|
|
24
|
+
runAudit,
|
|
25
|
+
schemaCoverage,
|
|
26
|
+
type AuditReport,
|
|
27
|
+
} from "@working-theory/aeo";
|
|
28
|
+
|
|
21
29
|
import { buildSinglePageAuditInput } from "../src/lib/audit-single-page.js";
|
|
22
30
|
|
|
23
31
|
const REPO_ROOT = resolve(__dirname, "../../..");
|
|
@@ -211,3 +219,69 @@ describe("structural guard — no fetch()", () => {
|
|
|
211
219
|
expect(codeOnly).not.toMatch(/\bfetch\s*\(/);
|
|
212
220
|
});
|
|
213
221
|
});
|
|
222
|
+
|
|
223
|
+
describe("buildSinglePageAuditInput — sitewide identity (#6597 AC-7)", () => {
|
|
224
|
+
const evidenceOf = (report: AuditReport, id: string) =>
|
|
225
|
+
report.perRule.find((r) => r.id === id);
|
|
226
|
+
const auditOf = async (input: { slug: string; type?: "AboutPage" }, root: string) =>
|
|
227
|
+
runAudit(await buildSinglePageAuditInput(input, root), [schemaCoverage, entityMarkup]);
|
|
228
|
+
|
|
229
|
+
it("schema-coverage passes for the monorepo's AboutPage/about", async () => {
|
|
230
|
+
const report = await auditOf({ slug: "about", type: "AboutPage" }, CONTENT_ROOT);
|
|
231
|
+
expect(evidenceOf(report, "schema-coverage")?.pass).toBe(true);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("the monorepo home finds the identity: Identity (organization) ✓", async () => {
|
|
235
|
+
const report = await auditOf({ slug: "home" }, CONTENT_ROOT);
|
|
236
|
+
expect(evidenceOf(report, "entity-markup")?.evidence).toContain(
|
|
237
|
+
"Identity (organization) ✓",
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("stores the flat node array, the page's own primary first", async () => {
|
|
242
|
+
const input = await buildSinglePageAuditInput({ slug: "about", type: "AboutPage" }, CONTENT_ROOT);
|
|
243
|
+
const nodes = input.jsonLdByPage["AboutPage/about"] as Array<Record<string, unknown>>;
|
|
244
|
+
expect(nodes[0]!["@type"]).toBe("AboutPage");
|
|
245
|
+
expect(nodes.map((n) => n["@type"])).toEqual(
|
|
246
|
+
expect.arrayContaining(["Organization", "WebSite"]),
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe("a Person site", () => {
|
|
251
|
+
let root: string;
|
|
252
|
+
beforeAll(() => {
|
|
253
|
+
root = mkdtempSync(join(tmpdir(), "mcp-6597-"));
|
|
254
|
+
writeFileSync(
|
|
255
|
+
join(root, "_site.mdx"),
|
|
256
|
+
[
|
|
257
|
+
"---",
|
|
258
|
+
'version: "1"',
|
|
259
|
+
"siteName: Ada",
|
|
260
|
+
"baseUrl: https://example.com",
|
|
261
|
+
"defaultLocale: en",
|
|
262
|
+
"siteIdentity:",
|
|
263
|
+
" kind: Person",
|
|
264
|
+
" name: Ada Lovelace",
|
|
265
|
+
" url: https://example.com",
|
|
266
|
+
"---",
|
|
267
|
+
"",
|
|
268
|
+
].join("\n"),
|
|
269
|
+
);
|
|
270
|
+
mkdirSync(join(root, "en", "HomePage"), { recursive: true });
|
|
271
|
+
writeFileSync(
|
|
272
|
+
join(root, "en", "HomePage", "home.mdx"),
|
|
273
|
+
["---", "type: HomePage", "slug: home", "title: Ada", "description: Ada's site.", "---", ""].join("\n"),
|
|
274
|
+
);
|
|
275
|
+
});
|
|
276
|
+
afterAll(() => {
|
|
277
|
+
rmSync(root, { recursive: true, force: true });
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("derives person mode from the kind: Identity (person) ✓", async () => {
|
|
281
|
+
const input = await buildSinglePageAuditInput({ slug: "home" }, root);
|
|
282
|
+
expect(input.identityMode).toBe("person");
|
|
283
|
+
const report = runAudit(input, [entityMarkup]);
|
|
284
|
+
expect(evidenceOf(report, "entity-markup")?.evidence).toContain("Identity (person) ✓");
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
});
|
|
@@ -10,18 +10,18 @@
|
|
|
10
10
|
* error, never logged. This is the ordinary "site has nothing of this
|
|
11
11
|
* type" case every real adapter (MDX, database) takes today.
|
|
12
12
|
* - a type whose adapter call THROWS is a genuine failure. It must not take
|
|
13
|
-
* down the audit (the other
|
|
13
|
+
* down the audit (the other two reads still complete and their pages
|
|
14
14
|
* still count), but it must be visible — logged via `console.error`
|
|
15
15
|
* (stderr, NOT stdout — the default stdio MCP transport's JSON-RPC
|
|
16
16
|
* framing channel, #6012 round 2) — rather than silently collapsing into
|
|
17
17
|
* the same vacuous "nothing found" result.
|
|
18
18
|
*
|
|
19
|
-
* The call-count assertions below prove that all
|
|
19
|
+
* The call-count assertions below prove that all three reads still ran despite
|
|
20
20
|
* the throw — the read AFTER the failing one is not skipped, which a
|
|
21
21
|
* sequential loop that ABORTED on the first throw would fail to show. They
|
|
22
22
|
* are NOT, on their own, proof of CONCURRENCY: the prior sequential `for`
|
|
23
23
|
* loop this replaced used `continue` (not a re-throw), so it too issued all
|
|
24
|
-
*
|
|
24
|
+
* three calls. The `Promise.all` in the source is what delivers the
|
|
25
25
|
* concurrency AC; these assertions cover the narrower, still load-bearing
|
|
26
26
|
* property that a throw part-way through never truncates the scan.
|
|
27
27
|
*/
|
|
@@ -93,10 +93,10 @@ describe("scanSitePages — type-not-registered vs adapter-threw (#6012)", () =>
|
|
|
93
93
|
|
|
94
94
|
expect(result.readiness!.sitePages).toEqual([]);
|
|
95
95
|
expect(errorSpy).not.toHaveBeenCalled();
|
|
96
|
-
// All
|
|
97
|
-
// listPages (
|
|
96
|
+
// All three reads still ran — two listDetail (Product, Service), one
|
|
97
|
+
// listPages (PurchasePolicyPage, #6646).
|
|
98
98
|
expect(listDetail).toHaveBeenCalledTimes(2);
|
|
99
|
-
expect(listPages).toHaveBeenCalledTimes(
|
|
99
|
+
expect(listPages).toHaveBeenCalledTimes(1);
|
|
100
100
|
});
|
|
101
101
|
|
|
102
102
|
it("a type whose adapter call throws is logged to stderr and does not fail the audit", async () => {
|
|
@@ -135,9 +135,57 @@ describe("scanSitePages — type-not-registered vs adapter-threw (#6012)", () =>
|
|
|
135
135
|
// the stdio transport's JSON-RPC channel).
|
|
136
136
|
expect(errorSpy).toHaveBeenCalledTimes(1);
|
|
137
137
|
expect(errorSpy.mock.calls[0]![0]).toMatch(/Service/);
|
|
138
|
-
// All
|
|
139
|
-
// throwing one (
|
|
138
|
+
// All three reads still ran despite the throw — the read AFTER the
|
|
139
|
+
// throwing one (PurchasePolicyPage) was not skipped.
|
|
140
140
|
expect(listDetail).toHaveBeenCalledTimes(2);
|
|
141
|
-
expect(listPages).toHaveBeenCalledTimes(
|
|
141
|
+
expect(listPages).toHaveBeenCalledTimes(1);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("a PurchasePolicyPage contributes its document-chassis section ids (#6646)", async () => {
|
|
145
|
+
const purchasePolicy: ContentPage = {
|
|
146
|
+
type: "PurchasePolicyPage",
|
|
147
|
+
slug: "purchase-policy",
|
|
148
|
+
title: "Purchase Policy",
|
|
149
|
+
description: "Returns, shipping and refunds.",
|
|
150
|
+
sections: [
|
|
151
|
+
{
|
|
152
|
+
id: "purchase-policy-document",
|
|
153
|
+
type: "document-chassis",
|
|
154
|
+
data: {
|
|
155
|
+
headline: "Purchase Policy",
|
|
156
|
+
sections: [
|
|
157
|
+
{ id: "returns", heading: "Returns", blocks: [] },
|
|
158
|
+
{ id: "shipping", heading: "Shipping", blocks: [] },
|
|
159
|
+
{ id: "refunds", heading: "Refunds", blocks: [] },
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
] as ContentPage["sections"],
|
|
164
|
+
};
|
|
165
|
+
mockResolveContentAdapter.mockReturnValue({
|
|
166
|
+
getPageBySlug: jest.fn().mockResolvedValue(HOME_PAGE),
|
|
167
|
+
listPages: jest.fn().mockResolvedValue([purchasePolicy]),
|
|
168
|
+
listDetail: jest.fn().mockResolvedValue([]),
|
|
169
|
+
getDetailBySlug: jest.fn(),
|
|
170
|
+
searchPages: jest.fn(),
|
|
171
|
+
extractStructuredData: jest.fn(),
|
|
172
|
+
createPage: jest.fn(),
|
|
173
|
+
updatePage: jest.fn(),
|
|
174
|
+
deletePage: jest.fn(),
|
|
175
|
+
getPageVersion: jest.fn(),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const result = await buildSinglePageAuditInput({
|
|
179
|
+
slug: "home",
|
|
180
|
+
type: "HomePage",
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(result.readiness!.sitePages).toEqual([
|
|
184
|
+
{
|
|
185
|
+
slug: "purchase-policy",
|
|
186
|
+
type: "PurchasePolicyPage",
|
|
187
|
+
chassisSectionIds: ["returns", "shipping", "refunds"],
|
|
188
|
+
},
|
|
189
|
+
]);
|
|
142
190
|
});
|
|
143
191
|
});
|
|
@@ -103,7 +103,6 @@ const MCP_ONLY_TOOLS = new Set([
|
|
|
103
103
|
"ailkGetPricing", // get_pricing — pure read, no action equivalent (I4)
|
|
104
104
|
"ailkGetFaq", // get_faq — pure read, no action equivalent (I4)
|
|
105
105
|
"ailkAudit", // audit — AEO scoring tool, no web action equivalent (I5; ADR 0013)
|
|
106
|
-
"ailkSubscribeNewsletter", // subscribe_newsletter — anonymous write via MCP; no Server Action (B7)
|
|
107
106
|
"ailkRequestCallback", // request_callback — anonymous write via MCP; no Server Action (B7)
|
|
108
107
|
"ailkCreateProject", // create_project — agent-only project creator; no Server Action equivalent (#299)
|
|
109
108
|
"ailkGetFlow", // get_flow — agent-callable flow-engine read; no web Server Action (#3956 D7)
|
|
@@ -228,6 +227,8 @@ describe("MCP ↔ Server-Action parity (filesystem)", () => {
|
|
|
228
227
|
// ailkSubmitFlowStep): twenty tools + three paired Server Actions.
|
|
229
228
|
// - ailkPurchase: paired with purchaseAction (apps/web/app/dev/purchase/).
|
|
230
229
|
// - ailkCaptureLead: paired with captureLeadAction (apps/web/app/contact/).
|
|
230
|
+
// - ailkSubscribeNewsletter: paired with subscribeNewsletterAction
|
|
231
|
+
// (apps/web/app/[locale]/newsletter/newsletter.actions.ts, #6730 D9).
|
|
231
232
|
// - ailkSubmitFlowStep: paired with submitFlowStepAction
|
|
232
233
|
// (apps/web/app/[locale]/flows/[slug]/flow.actions.ts, #3957).
|
|
233
234
|
// - startCheckoutAction, saveRecommendationAction: WEB-ONLY (#4842 D7,
|
|
@@ -241,8 +242,8 @@ describe("MCP ↔ Server-Action parity (filesystem)", () => {
|
|
|
241
242
|
// ailkGetLocations, ailkGetOfferings, ailkGetPricing, ailkGetFaq:
|
|
242
243
|
// MCP-only (pure read; I4; in MCP_ONLY_TOOLS allow-list).
|
|
243
244
|
// - ailkAudit: MCP-only (AEO scoring; I5; in MCP_ONLY_TOOLS allow-list).
|
|
244
|
-
// -
|
|
245
|
-
//
|
|
245
|
+
// - ailkRequestCallback: MCP-only anonymous-write tool (B7; in
|
|
246
|
+
// MCP_ONLY_TOOLS allow-list).
|
|
246
247
|
// - ailkCreateProject: MCP-only agent project creator (#299; in MCP_ONLY_TOOLS).
|
|
247
248
|
// - ailkCreatePage, ailkUpdatePage, ailkDeletePage: RETIRED by #5853
|
|
248
249
|
// (D7). Their HTTP twins — the untenanted /v1/content writes — were
|
|
@@ -264,7 +265,7 @@ describe("MCP ↔ Server-Action parity (filesystem)", () => {
|
|
|
264
265
|
// - ailkGetEventMeta: MCP-only meta-only scheduling read, twin of
|
|
265
266
|
// GET /v1/schedule/event-meta (#4633) — same posture as the other
|
|
266
267
|
// scheduling tools.
|
|
267
|
-
expect(actions.size).toBe(
|
|
268
|
+
expect(actions.size).toBe(6);
|
|
268
269
|
expect(tools.size).toBe(20);
|
|
269
270
|
expect(r.missingTools).toEqual([]);
|
|
270
271
|
expect(r.orphanTools).toEqual([]);
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"lint": "eslint .",
|
|
10
10
|
"build": "tsc -b",
|
|
11
11
|
"type-check": "tsc --noEmit",
|
|
12
|
-
"dev": "
|
|
12
|
+
"dev": "tsx watch src/index.ts",
|
|
13
|
+
"start": "node --experimental-vm-modules dist/index.js",
|
|
13
14
|
"test": "jest",
|
|
14
15
|
"test:watch": "jest --watch",
|
|
15
16
|
"test:coverage": "jest --coverage"
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { initMcpCommerceClaim } from "./lib/commerce-claim.js";
|
|
25
|
+
import { registerLicenseKeyStore } from "./lib/license-key-store.js";
|
|
25
26
|
import { initMcpPublicWorkspace } from "./lib/public-workspace.js";
|
|
26
27
|
|
|
27
28
|
// Resolves the MCP server's own workspace ONCE, before either transport
|
|
@@ -48,6 +49,12 @@ try {
|
|
|
48
49
|
);
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
// Registers the renewed licence key's durable home (#6677 DD-6) BEFORE the
|
|
53
|
+
// commerce claim below memoises the licence: with a database, the key a
|
|
54
|
+
// sibling app renewed lives in one SiteSetting row, and the claim must read
|
|
55
|
+
// it. Never throws; without DATABASE_URL the file store stays in place.
|
|
56
|
+
await registerLicenseKeyStore();
|
|
57
|
+
|
|
51
58
|
// Resolves the deployment's `commerce` add-on claim ONCE, before either
|
|
52
59
|
// transport branch — the sibling of the workspace resolution above and the
|
|
53
60
|
// same #5876 idiom (#5971 spec D2). Without this await the memoised claim is
|