create-ailk 0.5.0 → 0.6.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 +55 -8
- package/component-catalog.json +336 -27
- package/component-catalog.md +25 -12
- package/dist/cli.js +54 -5
- package/dist/copy.d.ts +14 -0
- package/dist/copy.js +38 -1
- package/dist/lib/api-boot-assertion.d.ts +46 -0
- package/dist/lib/api-boot-assertion.js +70 -0
- package/dist/lib/extract-module-bundle.d.ts +20 -1
- package/dist/lib/extract-module-bundle.js +64 -5
- package/dist/lib/import-graph.d.ts +29 -0
- package/dist/lib/import-graph.js +140 -0
- package/dist/lib/module-license-gate.d.ts +6 -1
- package/dist/lib/module-license-gate.js +6 -1
- package/dist/lib/pro-callout.js +1 -1
- package/dist/module-architecture.d.ts +1 -0
- package/dist/module-architecture.js +253 -127
- package/dist/modules/commerce/0.6.0.tar.gz +0 -0
- package/dist/modules/docs-blog/0.6.0.tar.gz +0 -0
- package/dist/modules/multi-tenant/0.6.0.tar.gz +0 -0
- package/dist/modules/scheduling/0.6.0.tar.gz +0 -0
- package/dist/modules/signal-collectors/0.6.0.tar.gz +0 -0
- package/dist/modules/workspace/0.6.0.tar.gz +0 -0
- package/dist/programmatic.js +14 -10
- package/dist/surfaces.js +105 -7
- package/dist/template-manifest.d.ts +33 -0
- package/dist/template-manifest.js +99 -16
- package/dist/template-names.d.ts +1 -1
- package/dist/template-names.js +1 -0
- package/package.json +3 -2
- package/site-templates/marketing-site/DESIGN.md +148 -0
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +22 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +9 -2
- package/site-templates/marketing-site/ailk-template.json +1 -1
- package/site-templates/marketing-site-free/DESIGN.md +148 -0
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +2 -2
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +1 -0
- package/site-templates/minimalism-site/DESIGN.md +148 -0
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +10 -2
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +47 -35
- package/site-templates/minimalism-site/ailk-template.json +2 -1
- package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
- package/site-templates/minimalism-site/content/en/HomePage/home.mdx +49 -19
- package/site-templates/saas-product/DESIGN.md +148 -0
- package/site-templates/simple-service/DESIGN.md +148 -0
- package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
- package/site-templates/simple-service/ailk-template.json +10 -0
- package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
- package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
- package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
- package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
- package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
- package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
- package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
- package/site-templates/simple-service/provenance.json +4 -0
- package/templates/.env.example +72 -7
- package/templates/CLAUDE.md +2 -3
- package/templates/DESIGN.md +155 -0
- package/templates/README.md +42 -8
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
- package/templates/apps/api/.env.example +13 -0
- package/templates/apps/api/CLAUDE.md +26 -2
- package/templates/apps/api/jest.config.cjs +3 -0
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +83 -7
- package/templates/apps/api/src/__tests__/server.test.ts +471 -0
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
- package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +2 -0
- package/templates/apps/api/src/config/index.ts +62 -0
- package/templates/apps/api/src/config/modules.ts +7 -3
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +238 -2
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
- package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
- package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
- package/templates/apps/api/src/lib/content-adapter.ts +57 -31
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
- package/templates/apps/api/src/lib/error-serializer.ts +23 -3
- package/templates/apps/api/src/lib/outbound-http.ts +7 -2
- package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
- package/templates/apps/api/src/lib/site.ts +208 -9
- package/templates/apps/api/src/lib/stripe.ts +66 -0
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +182 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +42 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
- package/templates/apps/api/src/openapi/spec.ts +543 -2
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
- package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
- package/templates/apps/api/src/routes/auth/user.ts +61 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
- package/templates/apps/api/src/routes/checkout/index.ts +34 -0
- package/templates/apps/api/src/routes/checkout/session.ts +139 -0
- package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
- package/templates/apps/api/src/routes/content/get-page.ts +94 -12
- package/templates/apps/api/src/routes/content/index.ts +13 -73
- package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
- package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
- package/templates/apps/api/src/routes/content/search.ts +129 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
- package/templates/apps/api/src/routes/leads/index.ts +100 -13
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
- package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
- package/templates/apps/api/src/routes/requests/index.ts +54 -0
- package/templates/apps/api/src/routes/webhooks/README.md +265 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
- package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
- package/templates/apps/api/src/server.ts +439 -49
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
- package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
- package/templates/apps/api/src/services/abandon-email.ts +8 -2
- package/templates/apps/api/src/services/aeo-score.ts +49 -12
- package/templates/apps/api/src/services/audit-log.ts +668 -0
- package/templates/apps/api/src/services/consent.ts +18 -0
- package/templates/apps/api/src/services/default-tenant.ts +70 -31
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
- package/templates/apps/api/src/services/dunning-policy.ts +294 -0
- package/templates/apps/api/src/services/entitlements.ts +80 -0
- package/templates/apps/api/src/services/lead-promotion.ts +13 -14
- package/templates/apps/api/src/services/lead-routing.ts +161 -2
- package/templates/apps/api/src/services/leads-inbox.ts +23 -2
- package/templates/apps/api/src/services/listing-config.ts +27 -9
- package/templates/apps/api/src/services/project-listings.ts +201 -6
- package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
- package/templates/apps/api/src/services/request-confirm.ts +646 -0
- package/templates/apps/api/src/services/revalidation.ts +173 -0
- package/templates/apps/api/src/services/verified-email.ts +71 -0
- package/templates/apps/api/src/vercel-handler.ts +75 -35
- package/templates/apps/mcp/.env.example +17 -0
- package/templates/apps/mcp/CLAUDE.md +57 -24
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
- package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
- package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
- package/templates/apps/mcp/__tests__/index.test.ts +39 -0
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
- package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
- package/templates/apps/mcp/__tests__/parity.test.ts +10 -7
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
- package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
- package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
- package/templates/apps/mcp/__tests__/server.test.ts +67 -14
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
- package/templates/apps/mcp/jest.config.cjs +13 -0
- package/templates/apps/mcp/package.json +2 -0
- package/templates/apps/mcp/src/abuse/index.ts +159 -24
- package/templates/apps/mcp/src/config/modules.ts +10 -5
- package/templates/apps/mcp/src/http-transport.ts +102 -29
- package/templates/apps/mcp/src/index.ts +73 -11
- package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
- package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
- package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
- package/templates/apps/mcp/src/lib/prisma.ts +39 -0
- package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
- package/templates/apps/mcp/src/server.ts +59 -35
- package/templates/apps/mcp/src/tools/audit.ts +26 -2
- package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
- package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
- package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
- package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
- package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
- package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
- package/templates/apps/mcp/src/tools/index.ts +6 -4
- package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
- package/templates/apps/mcp/src/tools/purchase.ts +128 -45
- package/templates/apps/mcp/src/tools/read_content.ts +20 -6
- package/templates/apps/mcp/src/tools/search_content.ts +11 -4
- package/templates/apps/web/.env.example +71 -5
- package/templates/apps/web/CLAUDE.md +44 -0
- package/templates/apps/web/PRODUCT.md +86 -0
- package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
- package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
- package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
- package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
- package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
- package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +63 -8
- package/templates/apps/web/app/[locale]/(authed)/layout.tsx +18 -1
- package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/layout.tsx +133 -87
- package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
- package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
- package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
- package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
- package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
- package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
- package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
- package/templates/apps/web/app/api/revalidate/route.ts +194 -0
- package/templates/apps/web/auth.ts +407 -8
- package/templates/apps/web/jest.config.cjs +8 -4
- package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
- package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +135 -20
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +18 -1
- package/templates/apps/web/lib/auth-email.ts +318 -0
- package/templates/apps/web/lib/auth-env.ts +85 -0
- package/templates/apps/web/lib/auth-routes.ts +55 -0
- package/templates/apps/web/lib/meeting-slugs.ts +53 -0
- package/templates/apps/web/lib/site-brand.tsx +4 -8
- package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
- package/templates/apps/web/messages/en.json +187 -0
- package/templates/apps/web/middleware.ts +1 -1
- package/templates/apps/web/package.json +2 -1
- package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
- package/templates/apps/web/src/lib/commerce-sections.ts +93 -0
- package/templates/content/_site.mdx +18 -3
- package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
- package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
- package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
- package/templates/database/CHANGELOG.md +149 -0
- package/templates/database/README.md +10 -3
- package/templates/database/__tests__/appointment.db.test.ts +106 -0
- package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
- package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
- package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
- package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
- package/templates/database/__tests__/tenancy.test.ts +141 -0
- package/templates/database/config/schema.prisma +79 -2
- package/templates/database/content/schema.prisma +87 -11
- package/templates/database/inbox/schema.prisma +452 -9
- package/templates/database/jest.config.cjs +5 -3
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
- package/templates/database/package.json +3 -2
- package/templates/database/tenancy.ts +86 -1
- package/templates/database/tsconfig.json +1 -0
- package/versions.json +34 -34
- package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
- package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
- package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
- package/templates/apps/api/src/routes/content/create.ts +0 -115
- package/templates/apps/api/src/routes/content/delete.ts +0 -82
- package/templates/apps/api/src/routes/content/update.ts +0 -112
- package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
- package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
- package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
- package/templates/apps/mcp/src/tools/create_page.ts +0 -96
- package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
- package/templates/apps/mcp/src/tools/update_page.ts +0 -92
- package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
- package/templates/apps/web/public/logo-dark.svg +0 -21
- package/templates/apps/web/public/logo-light.svg +0 -21
- /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
- /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
|
@@ -8,19 +8,83 @@
|
|
|
8
8
|
* - ailkReadContent returns null (not throws) for an unknown slug
|
|
9
9
|
* - Output validates against readContentOutputSchema
|
|
10
10
|
* - No console.* calls in handler (structural test)
|
|
11
|
+
* - #6030 AC-1: BlogPosting and DocsDetail — the two types this issue admits —
|
|
12
|
+
* are readable through the adapter on both the MDX leg and the database
|
|
13
|
+
* leg, and both are asserted against the shared adapter seam
|
|
14
|
+
* (`ContentAdapter.getPageSource`) per the API↔MCP parity contract (D3).
|
|
11
15
|
*/
|
|
12
16
|
|
|
13
|
-
import { readFileSync } from "node:fs";
|
|
17
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync } from "node:fs";
|
|
18
|
+
import { tmpdir } from "node:os";
|
|
14
19
|
import { resolve, join } from "node:path";
|
|
15
20
|
|
|
21
|
+
import {
|
|
22
|
+
createMdxAdapter,
|
|
23
|
+
resetResolvedContentAdapter,
|
|
24
|
+
type ArtifactPrismaClient,
|
|
25
|
+
} from "@working-theory/content-adapters";
|
|
26
|
+
|
|
16
27
|
import {
|
|
17
28
|
ailkReadContent,
|
|
18
29
|
readContentInputSchema,
|
|
19
30
|
readContentOutputSchema,
|
|
20
31
|
} from "../src/tools/read_content.js";
|
|
32
|
+
import {
|
|
33
|
+
__setMcpPublicWorkspaceForTest,
|
|
34
|
+
resetMcpPublicWorkspace,
|
|
35
|
+
} from "../src/lib/public-workspace.js";
|
|
21
36
|
|
|
22
37
|
const REPO_ROOT = resolve(__dirname, "../../..");
|
|
23
38
|
const CONTENT_ROOT = join(REPO_ROOT, "content");
|
|
39
|
+
const WORKSPACE_ID = "wksp_00000000-0000-7000-8000-000000000001";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A structural `ArtifactPrismaClient` stub (#5876 spec D11) — `artifact` and
|
|
43
|
+
* `artifactVersion` jest.fn()s, injected directly, no database.
|
|
44
|
+
*/
|
|
45
|
+
function makeArtifactPrismaClientStub(overrides: {
|
|
46
|
+
artifact?: { publishedVersionId: string | null } | null;
|
|
47
|
+
version?: { content: unknown } | null;
|
|
48
|
+
}): ArtifactPrismaClient {
|
|
49
|
+
const artifact =
|
|
50
|
+
overrides.artifact === undefined || overrides.artifact === null
|
|
51
|
+
? null
|
|
52
|
+
: {
|
|
53
|
+
id: "artifact_1",
|
|
54
|
+
workspaceId: WORKSPACE_ID,
|
|
55
|
+
contentType: "text/page",
|
|
56
|
+
locale: "en",
|
|
57
|
+
slug: "home",
|
|
58
|
+
title: "Home",
|
|
59
|
+
publishedVersionId: overrides.artifact.publishedVersionId,
|
|
60
|
+
};
|
|
61
|
+
const version =
|
|
62
|
+
overrides.version === undefined || overrides.version === null
|
|
63
|
+
? null
|
|
64
|
+
: {
|
|
65
|
+
id: "version_1",
|
|
66
|
+
artifactId: "artifact_1",
|
|
67
|
+
workspaceId: WORKSPACE_ID,
|
|
68
|
+
content: overrides.version.content,
|
|
69
|
+
isCurrent: false,
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
artifact: {
|
|
73
|
+
findFirst: jest.fn().mockResolvedValue(artifact),
|
|
74
|
+
findMany: jest.fn().mockResolvedValue([]),
|
|
75
|
+
create: jest.fn(),
|
|
76
|
+
updateMany: jest.fn(),
|
|
77
|
+
deleteMany: jest.fn(),
|
|
78
|
+
},
|
|
79
|
+
artifactVersion: {
|
|
80
|
+
findFirst: jest.fn().mockResolvedValue(version),
|
|
81
|
+
findMany: jest.fn().mockResolvedValue([]),
|
|
82
|
+
create: jest.fn(),
|
|
83
|
+
updateMany: jest.fn(),
|
|
84
|
+
},
|
|
85
|
+
$transaction: jest.fn(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
24
88
|
|
|
25
89
|
describe("readContentInputSchema", () => {
|
|
26
90
|
it("accepts a valid type + slug", () => {
|
|
@@ -142,6 +206,263 @@ describe("ailkReadContent handler", () => {
|
|
|
142
206
|
});
|
|
143
207
|
});
|
|
144
208
|
|
|
209
|
+
describe("ailkReadContent — CONTENT_ADAPTER=database (#5876)", () => {
|
|
210
|
+
const previous = process.env.CONTENT_ADAPTER;
|
|
211
|
+
|
|
212
|
+
beforeEach(() => {
|
|
213
|
+
process.env.CONTENT_ADAPTER = "database";
|
|
214
|
+
// resolveContentAdapter() memoises per (kind, workspaceId, contentRoot,
|
|
215
|
+
// defaultLocale, manifestPath) — NOT per prisma instance — so every case
|
|
216
|
+
// below (same workspaceId, same defaults) would otherwise reuse the FIRST
|
|
217
|
+
// test's adapter, bound to the FIRST test's stub.
|
|
218
|
+
resetResolvedContentAdapter();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
afterEach(() => {
|
|
222
|
+
if (previous === undefined) delete process.env.CONTENT_ADAPTER;
|
|
223
|
+
else process.env.CONTENT_ADAPTER = previous;
|
|
224
|
+
resetMcpPublicWorkspace();
|
|
225
|
+
resetResolvedContentAdapter();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("AC-1: returns { slug, type, frontmatter, body } for a published page", async () => {
|
|
229
|
+
const stub = makeArtifactPrismaClientStub({
|
|
230
|
+
artifact: { publishedVersionId: "version_1" },
|
|
231
|
+
version: {
|
|
232
|
+
content: {
|
|
233
|
+
type: "HomePage",
|
|
234
|
+
slug: "home",
|
|
235
|
+
title: "Home",
|
|
236
|
+
description: "The homepage.",
|
|
237
|
+
body: "# Hello",
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
__setMcpPublicWorkspaceForTest({
|
|
242
|
+
kind: "resolved",
|
|
243
|
+
workspaceId: WORKSPACE_ID,
|
|
244
|
+
prisma: stub,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const result = await ailkReadContent({ type: "HomePage", slug: "home" });
|
|
248
|
+
|
|
249
|
+
expect(result).not.toBeNull();
|
|
250
|
+
expect(result!.slug).toBe("home");
|
|
251
|
+
expect(result!.type).toBe("HomePage");
|
|
252
|
+
expect(result!.body).toBe("# Hello");
|
|
253
|
+
// The reach test (§5.2 AC-1) — the read is bound to the resolved workspace.
|
|
254
|
+
expect(stub.artifact.findFirst).toHaveBeenCalledWith(
|
|
255
|
+
expect.objectContaining({
|
|
256
|
+
where: expect.objectContaining({ workspaceId: WORKSPACE_ID }),
|
|
257
|
+
}),
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("AC-1: returns null for a draft-only artifact (no published version)", async () => {
|
|
262
|
+
const stub = makeArtifactPrismaClientStub({
|
|
263
|
+
artifact: { publishedVersionId: null },
|
|
264
|
+
});
|
|
265
|
+
__setMcpPublicWorkspaceForTest({
|
|
266
|
+
kind: "resolved",
|
|
267
|
+
workspaceId: WORKSPACE_ID,
|
|
268
|
+
prisma: stub,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const result = await ailkReadContent({ type: "HomePage", slug: "home" });
|
|
272
|
+
|
|
273
|
+
expect(result).toBeNull();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("AC-1: returns null for an unknown slug", async () => {
|
|
277
|
+
const stub = makeArtifactPrismaClientStub({ artifact: null });
|
|
278
|
+
__setMcpPublicWorkspaceForTest({
|
|
279
|
+
kind: "resolved",
|
|
280
|
+
workspaceId: WORKSPACE_ID,
|
|
281
|
+
prisma: stub,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const result = await ailkReadContent({
|
|
285
|
+
type: "HomePage",
|
|
286
|
+
slug: "does-not-exist",
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
expect(result).toBeNull();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("AC-2: returns null when the workspace is unresolved, with no content lookup", async () => {
|
|
293
|
+
const stub = makeArtifactPrismaClientStub({});
|
|
294
|
+
__setMcpPublicWorkspaceForTest({ kind: "unresolved", reason: "ambiguous" });
|
|
295
|
+
|
|
296
|
+
const result = await ailkReadContent({ type: "HomePage", slug: "home" });
|
|
297
|
+
|
|
298
|
+
expect(result).toBeNull();
|
|
299
|
+
expect(stub.artifact.findFirst).not.toHaveBeenCalled();
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
describe("ailkReadContent — BlogPosting and DocsDetail, MDX leg (#6030 AC-1)", () => {
|
|
304
|
+
let root = "";
|
|
305
|
+
|
|
306
|
+
beforeAll(() => {
|
|
307
|
+
root = mkdtempSync(join(tmpdir(), "ailk-mcp-6030-"));
|
|
308
|
+
for (const { type, slug, body } of [
|
|
309
|
+
{ type: "BlogPosting", slug: "launch-post", body: "# Launch post\n\nBody." },
|
|
310
|
+
{ type: "DocsDetail", slug: "getting-started", body: "# Getting started\n\nBody." },
|
|
311
|
+
]) {
|
|
312
|
+
const dir = join(root, "en", type);
|
|
313
|
+
mkdirSync(dir, { recursive: true });
|
|
314
|
+
writeFileSync(
|
|
315
|
+
join(dir, `${slug}.mdx`),
|
|
316
|
+
[
|
|
317
|
+
"---",
|
|
318
|
+
`type: "${type}"`,
|
|
319
|
+
`slug: "${slug}"`,
|
|
320
|
+
`title: "${type} ${slug} title"`,
|
|
321
|
+
`description: "${type} ${slug} description"`,
|
|
322
|
+
"---",
|
|
323
|
+
"",
|
|
324
|
+
body,
|
|
325
|
+
"",
|
|
326
|
+
].join("\n"),
|
|
327
|
+
"utf8",
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
afterAll(() => {
|
|
333
|
+
rmSync(root, { recursive: true, force: true });
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it.each(["BlogPosting", "DocsDetail"] as const)(
|
|
337
|
+
"returns the page for %s, deep-equal to the projection over getPageSource (D3)",
|
|
338
|
+
async (type) => {
|
|
339
|
+
const slug = type === "BlogPosting" ? "launch-post" : "getting-started";
|
|
340
|
+
const result = await ailkReadContent({ type, slug, locale: "en" }, root);
|
|
341
|
+
|
|
342
|
+
// The shared seam D3 asserts from both ends: read_content's payload is
|
|
343
|
+
// derived from exactly what ContentAdapter.getPageSource(type, slug,
|
|
344
|
+
// locale) returns for the arguments the tool builds — not restated.
|
|
345
|
+
const adapter = createMdxAdapter({ contentRoot: root });
|
|
346
|
+
const source = await adapter.getPageSource(type, slug, "en");
|
|
347
|
+
expect(source).not.toBeNull();
|
|
348
|
+
const expected = readContentOutputSchema.parse({
|
|
349
|
+
slug: source!.frontmatter.slug ?? slug,
|
|
350
|
+
type: source!.frontmatter.type ?? type,
|
|
351
|
+
frontmatter: source!.frontmatter,
|
|
352
|
+
body: source!.body,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
expect(result).toEqual(expected);
|
|
356
|
+
expect(result!.slug).toBe(slug);
|
|
357
|
+
expect(result!.type).toBe(type);
|
|
358
|
+
},
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
it.each(["BlogPosting", "DocsDetail"] as const)(
|
|
362
|
+
"returns null for an absent slug of %s",
|
|
363
|
+
async (type) => {
|
|
364
|
+
const result = await ailkReadContent(
|
|
365
|
+
{ type, slug: "does-not-exist", locale: "en" },
|
|
366
|
+
root,
|
|
367
|
+
);
|
|
368
|
+
expect(result).toBeNull();
|
|
369
|
+
},
|
|
370
|
+
);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
describe("ailkReadContent — BlogPosting and DocsDetail, CONTENT_ADAPTER=database (#6030 AC-1)", () => {
|
|
374
|
+
const previous = process.env.CONTENT_ADAPTER;
|
|
375
|
+
|
|
376
|
+
beforeEach(() => {
|
|
377
|
+
process.env.CONTENT_ADAPTER = "database";
|
|
378
|
+
resetResolvedContentAdapter();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
afterEach(() => {
|
|
382
|
+
if (previous === undefined) delete process.env.CONTENT_ADAPTER;
|
|
383
|
+
else process.env.CONTENT_ADAPTER = previous;
|
|
384
|
+
resetMcpPublicWorkspace();
|
|
385
|
+
resetResolvedContentAdapter();
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it.each(["BlogPosting", "DocsDetail"] as const)(
|
|
389
|
+
"returns { slug, type, frontmatter, body } for a published %s",
|
|
390
|
+
async (type) => {
|
|
391
|
+
const slug = "probe";
|
|
392
|
+
const stub = makeArtifactPrismaClientStub({
|
|
393
|
+
artifact: { publishedVersionId: "version_1" },
|
|
394
|
+
version: {
|
|
395
|
+
content: {
|
|
396
|
+
type,
|
|
397
|
+
slug,
|
|
398
|
+
title: `${type} title`,
|
|
399
|
+
description: `${type} description`,
|
|
400
|
+
body: "# Published body",
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
__setMcpPublicWorkspaceForTest({
|
|
405
|
+
kind: "resolved",
|
|
406
|
+
workspaceId: WORKSPACE_ID,
|
|
407
|
+
prisma: stub,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const result = await ailkReadContent({ type, slug });
|
|
411
|
+
|
|
412
|
+
expect(result).not.toBeNull();
|
|
413
|
+
expect(result!.slug).toBe(slug);
|
|
414
|
+
expect(result!.type).toBe(type);
|
|
415
|
+
expect(result!.body).toBe("# Published body");
|
|
416
|
+
},
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
// D4 — the publication guard must hold for every newly-admitted type, not
|
|
420
|
+
// only HomePage's existing case.
|
|
421
|
+
it.each(["BlogPosting", "DocsDetail"] as const)(
|
|
422
|
+
"returns null for a draft-only %s artifact (no published version)",
|
|
423
|
+
async (type) => {
|
|
424
|
+
const stub = makeArtifactPrismaClientStub({
|
|
425
|
+
artifact: { publishedVersionId: null },
|
|
426
|
+
});
|
|
427
|
+
__setMcpPublicWorkspaceForTest({
|
|
428
|
+
kind: "resolved",
|
|
429
|
+
workspaceId: WORKSPACE_ID,
|
|
430
|
+
prisma: stub,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const result = await ailkReadContent({ type, slug: "probe" });
|
|
434
|
+
|
|
435
|
+
expect(result).toBeNull();
|
|
436
|
+
},
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
// D4 — type-as-address: a page stored under a different frontmatter.type
|
|
440
|
+
// than requested reads as absent, never as a cross-type hit.
|
|
441
|
+
it("returns null when the requested type does not match the stored frontmatter.type", async () => {
|
|
442
|
+
const stub = makeArtifactPrismaClientStub({
|
|
443
|
+
artifact: { publishedVersionId: "version_1" },
|
|
444
|
+
version: {
|
|
445
|
+
content: {
|
|
446
|
+
type: "BlogPosting",
|
|
447
|
+
slug: "probe",
|
|
448
|
+
title: "A blog post",
|
|
449
|
+
description: "Stored as BlogPosting.",
|
|
450
|
+
body: "# Body",
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
__setMcpPublicWorkspaceForTest({
|
|
455
|
+
kind: "resolved",
|
|
456
|
+
workspaceId: WORKSPACE_ID,
|
|
457
|
+
prisma: stub,
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
const result = await ailkReadContent({ type: "DocsDetail", slug: "probe" });
|
|
461
|
+
|
|
462
|
+
expect(result).toBeNull();
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
|
|
145
466
|
describe("architectural guard — no console calls", () => {
|
|
146
467
|
it("apps/mcp/src/tools/read_content.ts does not call console.*", () => {
|
|
147
468
|
const src = readFileSync(
|
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
* - ailkSearchContent happy path returns results array with correct shape
|
|
10
10
|
* - ailkSearchContent returns empty results for a query with no matches
|
|
11
11
|
* - Malformed input causes Zod rejection
|
|
12
|
+
* - #5831: searchContentInputSchema accepts a registry category slug and
|
|
13
|
+
* rejects one outside the registry; the handler forwards it to the adapter
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
|
-
import { readFileSync } from "node:fs";
|
|
15
|
-
import {
|
|
16
|
+
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import { join, resolve } from "node:path";
|
|
16
19
|
|
|
17
20
|
import {
|
|
18
21
|
ailkSearchContent,
|
|
@@ -100,6 +103,106 @@ describe("searchContentInputSchema", () => {
|
|
|
100
103
|
const result = searchContentInputSchema.safeParse({});
|
|
101
104
|
expect(result.success).toBe(false);
|
|
102
105
|
});
|
|
106
|
+
|
|
107
|
+
it("accepts a category slug from the registry (#5831)", () => {
|
|
108
|
+
const result = searchContentInputSchema.safeParse({
|
|
109
|
+
query: "roof",
|
|
110
|
+
category: "roofing",
|
|
111
|
+
});
|
|
112
|
+
expect(result.success).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("rejects a category slug outside the registry with an enum error (#5831)", () => {
|
|
116
|
+
const result = searchContentInputSchema.safeParse({
|
|
117
|
+
query: "roof",
|
|
118
|
+
category: "not-a-real-category",
|
|
119
|
+
});
|
|
120
|
+
expect(result.success).toBe(false);
|
|
121
|
+
expect(result.error!.issues[0]).toMatchObject({
|
|
122
|
+
code: "invalid_value",
|
|
123
|
+
path: ["category"],
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("rejects a GROUP slug — groups are derived, never tagged (#5831)", () => {
|
|
128
|
+
const result = searchContentInputSchema.safeParse({
|
|
129
|
+
query: "roof",
|
|
130
|
+
category: "professional-services",
|
|
131
|
+
});
|
|
132
|
+
expect(result.success).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("ailkSearchContent — category filter (#5831)", () => {
|
|
137
|
+
let root = "";
|
|
138
|
+
|
|
139
|
+
beforeAll(() => {
|
|
140
|
+
root = mkdtempSync(join(tmpdir(), "ailk-mcp-category-"));
|
|
141
|
+
const dir = join(root, "en", "Product");
|
|
142
|
+
mkdirSync(dir, { recursive: true });
|
|
143
|
+
writeFileSync(
|
|
144
|
+
join(dir, "roof-replacement.mdx"),
|
|
145
|
+
[
|
|
146
|
+
"---",
|
|
147
|
+
'type: "Product"',
|
|
148
|
+
'slug: "roof-replacement"',
|
|
149
|
+
'title: "Roof Replacement"',
|
|
150
|
+
'description: "A full roof replacement service."',
|
|
151
|
+
"categories:",
|
|
152
|
+
" - roofing",
|
|
153
|
+
"---",
|
|
154
|
+
"",
|
|
155
|
+
"We replace a roof in a day.",
|
|
156
|
+
"",
|
|
157
|
+
].join("\n"),
|
|
158
|
+
"utf8",
|
|
159
|
+
);
|
|
160
|
+
writeFileSync(
|
|
161
|
+
join(dir, "attic-ventilation.mdx"),
|
|
162
|
+
[
|
|
163
|
+
"---",
|
|
164
|
+
'type: "Product"',
|
|
165
|
+
'slug: "attic-ventilation"',
|
|
166
|
+
'title: "Attic Ventilation"',
|
|
167
|
+
'description: "Ventilation that protects the roof deck."',
|
|
168
|
+
"categories:",
|
|
169
|
+
" - hvac",
|
|
170
|
+
"---",
|
|
171
|
+
"",
|
|
172
|
+
"Attic airflow keeps a roof healthy.",
|
|
173
|
+
"",
|
|
174
|
+
].join("\n"),
|
|
175
|
+
"utf8",
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
afterAll(() => {
|
|
180
|
+
rmSync(root, { recursive: true, force: true });
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("forwards category to the adapter — only the tagged page comes back", async () => {
|
|
184
|
+
const result = await ailkSearchContent(
|
|
185
|
+
{ query: "roof", category: "roofing" },
|
|
186
|
+
root,
|
|
187
|
+
);
|
|
188
|
+
expect(result.results.map((r) => r.slug)).toEqual(["roof-replacement"]);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("returns every match when no category is passed", async () => {
|
|
192
|
+
const result = await ailkSearchContent({ query: "roof" }, root);
|
|
193
|
+
expect(result.results.map((r) => r.slug).sort()).toEqual([
|
|
194
|
+
"attic-ventilation",
|
|
195
|
+
"roof-replacement",
|
|
196
|
+
]);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("returns [] for a registry slug nothing is tagged with", async () => {
|
|
200
|
+
const result = await ailkSearchContent(
|
|
201
|
+
{ query: "roof", category: "legal" },
|
|
202
|
+
root,
|
|
203
|
+
);
|
|
204
|
+
expect(result.results).toEqual([]);
|
|
205
|
+
});
|
|
103
206
|
});
|
|
104
207
|
|
|
105
208
|
describe("ailkSearchContent handler", () => {
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* - list_pages and read_content tools are registered
|
|
7
7
|
* - Each registered tool has a non-empty description
|
|
8
8
|
*
|
|
9
|
-
* Note: `start_purchase`
|
|
10
|
-
* `purchase-disabled.test.ts`
|
|
11
|
-
*
|
|
9
|
+
* Note: the full `start_purchase` claim MATRIX — absent on a keyless boot,
|
|
10
|
+
* present on a keyed one — is asserted in `purchase-disabled.test.ts`
|
|
11
|
+
* (#5971, superseding S15a / issue #610). The describe block at the end of
|
|
12
|
+
* this file pins the registration half from the bootstrap's own side.
|
|
12
13
|
*
|
|
13
14
|
* Strategy: createMcpServer() returns the McpServer instance before connecting
|
|
14
15
|
* to a transport. Tests inspect server._registeredTools (plain object in SDK
|
|
@@ -17,7 +18,11 @@
|
|
|
17
18
|
* casts below. Pinned to @modelcontextprotocol/sdk ^1.29.0.
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
__setMcpCommerceClaimForTest,
|
|
23
|
+
resetMcpCommerceClaim,
|
|
24
|
+
} from "../src/lib/commerce-claim.js";
|
|
25
|
+
import { createMcpServer, listRegisteredTools } from "../src/server.js";
|
|
21
26
|
|
|
22
27
|
describe("createMcpServer", () => {
|
|
23
28
|
it("creates an McpServer without throwing", () => {
|
|
@@ -90,26 +95,74 @@ const I4_TOOLS = [
|
|
|
90
95
|
"get_faq",
|
|
91
96
|
] as const;
|
|
92
97
|
|
|
93
|
-
describe.each(I4_TOOLS.map((name) => [name]))(
|
|
94
|
-
|
|
98
|
+
describe.each(I4_TOOLS.map((name) => [name]))(
|
|
99
|
+
"%s tool registration",
|
|
100
|
+
(toolName) => {
|
|
101
|
+
it(`server has the ${toolName} tool registered`, () => {
|
|
102
|
+
const server = createMcpServer();
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- #506/#507: pre-existing test-hygiene violation
|
|
104
|
+
const tools = (server as any)._registeredTools as Record<string, unknown>;
|
|
105
|
+
expect(toolName in tools).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it(`${toolName} descriptor has a non-empty description`, () => {
|
|
109
|
+
const server = createMcpServer();
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- #506/#507: pre-existing test-hygiene violation
|
|
111
|
+
const tools = (server as any)._registeredTools as Record<
|
|
112
|
+
string,
|
|
113
|
+
{ description?: string }
|
|
114
|
+
>;
|
|
115
|
+
const tool = tools[toolName];
|
|
116
|
+
expect(tool).toBeDefined();
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- #506/#507: pre-existing test-hygiene violation
|
|
118
|
+
expect(typeof (tool as any).description).toBe("string");
|
|
119
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- #506/#507: pre-existing test-hygiene violation
|
|
120
|
+
expect((tool as any).description.length).toBeGreaterThan(0);
|
|
121
|
+
});
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
describe("start_purchase registration — gated on the commerce claim (#5971)", () => {
|
|
126
|
+
afterEach(() => {
|
|
127
|
+
resetMcpCommerceClaim();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("is not registered when the deployment holds no commerce claim", () => {
|
|
131
|
+
__setMcpCommerceClaimForTest("unclaimed");
|
|
95
132
|
const server = createMcpServer();
|
|
96
133
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- #506/#507: pre-existing test-hygiene violation
|
|
97
134
|
const tools = (server as any)._registeredTools as Record<string, unknown>;
|
|
98
|
-
|
|
135
|
+
|
|
136
|
+
expect("start_purchase" in tools).toBe(false);
|
|
137
|
+
expect(listRegisteredTools().map((t) => t.name)).not.toContain(
|
|
138
|
+
"start_purchase",
|
|
139
|
+
);
|
|
99
140
|
});
|
|
100
141
|
|
|
101
|
-
it(
|
|
142
|
+
it("is registered, with a non-empty description, when the claim is held", () => {
|
|
143
|
+
__setMcpCommerceClaimForTest("claimed");
|
|
102
144
|
const server = createMcpServer();
|
|
103
145
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- #506/#507: pre-existing test-hygiene violation
|
|
104
146
|
const tools = (server as any)._registeredTools as Record<
|
|
105
147
|
string,
|
|
106
148
|
{ description?: string }
|
|
107
149
|
>;
|
|
108
|
-
|
|
109
|
-
expect(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
150
|
+
|
|
151
|
+
expect("start_purchase" in tools).toBe(true);
|
|
152
|
+
expect(tools["start_purchase"]?.description ?? "").not.toBe("");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("registering it never changes what the other tools do", () => {
|
|
156
|
+
__setMcpCommerceClaimForTest("unclaimed");
|
|
157
|
+
const keyless = listRegisteredTools()
|
|
158
|
+
.map((t) => t.name)
|
|
159
|
+
.sort();
|
|
160
|
+
|
|
161
|
+
__setMcpCommerceClaimForTest("claimed");
|
|
162
|
+
const keyed = listRegisteredTools()
|
|
163
|
+
.map((t) => t.name)
|
|
164
|
+
.sort();
|
|
165
|
+
|
|
166
|
+
expect(keyed).toEqual([...keyless, "start_purchase"].sort());
|
|
114
167
|
});
|
|
115
168
|
});
|