create-ailk 0.30.0 → 0.32.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.
Files changed (95) hide show
  1. package/README.md +108 -4
  2. package/component-catalog.json +524 -8
  3. package/component-catalog.md +8 -5
  4. package/dist/body-heading.d.ts +13 -0
  5. package/dist/body-heading.js +19 -5
  6. package/dist/cli.js +99 -0
  7. package/dist/cutover.d.ts +114 -0
  8. package/dist/cutover.js +511 -0
  9. package/dist/design-presets.js +20 -4
  10. package/dist/export-content.d.ts +55 -0
  11. package/dist/export-content.js +303 -0
  12. package/dist/inventory.d.ts +216 -0
  13. package/dist/inventory.js +833 -0
  14. package/dist/language-import.d.ts +127 -0
  15. package/dist/language-import.js +451 -0
  16. package/dist/lib/env-catalog.js +1 -1
  17. package/dist/lib/import-mdx.d.ts +38 -0
  18. package/dist/lib/import-mdx.js +445 -65
  19. package/dist/lib/render-comparison.d.ts +251 -0
  20. package/dist/lib/render-comparison.js +573 -0
  21. package/dist/mdx-braces.d.ts +73 -0
  22. package/dist/mdx-braces.js +285 -0
  23. package/dist/migrate-content.d.ts +6 -2
  24. package/dist/migrate-content.js +62 -14
  25. package/dist/programmatic.js +2 -8
  26. package/dist/seed-workspace.d.ts +3 -1
  27. package/dist/seed-workspace.js +45 -16
  28. package/dist/site-manifest.js +6 -38
  29. package/dist/site-roster.d.ts +46 -3
  30. package/dist/site-roster.js +124 -4
  31. package/dist/surfaces.d.ts +8 -0
  32. package/dist/surfaces.js +16 -0
  33. package/dist/topology.d.ts +12 -0
  34. package/dist/topology.js +24 -10
  35. package/dist/update.d.ts +21 -1
  36. package/dist/update.js +142 -11
  37. package/package.json +3 -3
  38. package/templates/.claude/cloudparity-repo-hooks.json +5 -0
  39. package/templates/.claude/hooks/content-source-guard.sh +75 -0
  40. package/templates/.claude/settings.json +9 -0
  41. package/templates/.github/workflows/ci.yml +4 -0
  42. package/templates/CLAUDE.md +5 -0
  43. package/templates/apps/mcp/.env.example +7 -1
  44. package/templates/apps/mcp/CLAUDE.md +8 -4
  45. package/templates/apps/web/.env.example +4 -2
  46. package/templates/apps/web/CLAUDE.md +16 -5
  47. package/templates/apps/web/__tests__/setup.ts +9 -0
  48. package/templates/apps/web/app/[locale]/[slug]/[detail]/__tests__/page.test.tsx +156 -4
  49. package/templates/apps/web/app/[locale]/[slug]/[detail]/page.tsx +45 -2
  50. package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +3 -0
  51. package/templates/apps/web/app/[locale]/about/__tests__/about-routes.test.tsx +3 -0
  52. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -0
  53. package/templates/apps/web/app/[locale]/contact/__tests__/site-contact-lines.test.ts +49 -0
  54. package/templates/apps/web/app/[locale]/contact/site-contact-lines.ts +6 -4
  55. package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +13 -1
  56. package/templates/apps/web/app/[locale]/events/__tests__/program-event.test.tsx +120 -0
  57. package/templates/apps/web/app/[locale]/events/page.tsx +11 -0
  58. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +44 -5
  59. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.thanks-by-tier.test.tsx +306 -0
  60. package/templates/apps/web/app/[locale]/layout.tsx +122 -27
  61. package/templates/apps/web/app/[locale]/pricing/[slug]/__tests__/page.test.tsx +3 -0
  62. package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +3 -0
  63. package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +3 -0
  64. package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +3 -0
  65. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +3 -0
  66. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -0
  67. package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +3 -0
  68. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +21 -2
  69. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +1 -0
  70. package/templates/apps/web/app/llms.txt/route.ts +16 -9
  71. package/templates/apps/web/app/sitemap.xml/route.ts +3 -0
  72. package/templates/apps/web/jest.config.cjs +46 -1
  73. package/templates/apps/web/lib/__tests__/bot-check.test.ts +247 -4
  74. package/templates/apps/web/lib/__tests__/in-page-magnet-self-request.test.ts +92 -0
  75. package/templates/apps/web/lib/__tests__/page-chrome.test.ts +93 -0
  76. package/templates/apps/web/lib/__tests__/relay.test.ts +33 -8
  77. package/templates/apps/web/lib/__tests__/site-identity-feature-list.test.ts +196 -0
  78. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +145 -11
  79. package/templates/apps/web/lib/bot-check.ts +113 -13
  80. package/templates/apps/web/lib/in-page-magnet.ts +4 -1
  81. package/templates/apps/web/lib/page-chrome.ts +65 -5
  82. package/templates/apps/web/lib/relay.ts +22 -3
  83. package/templates/apps/web/lib/site-identity.ts +75 -24
  84. package/templates/apps/web/messages/en.json +5 -1
  85. package/templates/apps/web/next.config.mjs +15 -1
  86. package/templates/apps/web/package.json +1 -1
  87. package/templates/apps/web/scripts/assert-content-bundled.ts +36 -8
  88. package/templates/apps/web/scripts/content-snapshot.ts +33 -0
  89. package/templates/apps/web/src/lib/routes.ts +5 -3
  90. package/templates/package.json +3 -1
  91. package/templates/tools/body-heading.ts +440 -0
  92. package/templates/tools/check-content-source.ts +108 -0
  93. package/templates/tools/check-mdx-body.ts +123 -0
  94. package/templates/tools/mdx-braces.ts +303 -0
  95. package/versions.json +48 -48
package/README.md CHANGED
@@ -393,6 +393,15 @@ merge-file` against the recorded version's own copy as the base. A clean
393
393
  **Added**, unless it sits under a route or directory the site deliberately
394
394
  does not carry (a gated-off surface, a spoke exclusion, or a directory you
395
395
  deleted entirely) — those are left alone.
396
+ - A surface no module owns (such as `projects`) that a page row of the
397
+ site's `.ailk-site-manifest.json` names, but `architecture.yaml` does not
398
+ declare, is in scope for adding: its route files are **Added** and
399
+ recorded, and `architecture.yaml` is regenerated to declare it. Reported
400
+ as **Surfaces declared from the roster**.
401
+ - A code-route row (`kind: "route"`) of `.ailk-site-manifest.json` that no
402
+ page file under `apps/web/app/[locale]/` serves (a spoke's `/sign-in`) is
403
+ dropped from the roster after the writes, every other byte of the file
404
+ kept. Reported as **Roster rows dropped, no route serves them**.
396
405
 
397
406
  A file outside the gated-off surfaces and spoke exclusions the site's scope
398
407
  no longer covers is **Skipped**, as is one that exists on disk but was never
@@ -425,7 +434,8 @@ never a shell. Fetching an old template version (when it is not the running
425
434
  package's own) reads the public npm registry at
426
435
  `https://registry.npmjs.org`, overridable with `AILK_NPM_REGISTRY_URL` for a
427
436
  mirror. `update` never touches content, except to give an mdx-body page
428
- with no `# ` heading its title and description (below), nor env files,
437
+ with no `# ` heading its title and description and to escape a bare `{` on
438
+ an mdx-body page (both below), nor env files,
429
439
  `.claude/`, lockfiles or any `package.json` — those stay `add-module`'s and
430
440
  the consumer version bump's job. Commit before running `update`, and review the diff after: a
431
441
  clean merge is still worth reading.
@@ -444,6 +454,27 @@ folded `>` block, say), is listed under **Needs review** for you to fix.
444
454
  Which pages are mdx-body is read from your own `apps/web/app` routes, and the
445
455
  step runs again harmlessly on every update.
446
456
 
457
+ **Placeholder braces (#10773).** MDX reads a `{` in a page body as
458
+ JavaScript, so on an mdx-body page a bare placeholder such as
459
+ `{First engagement}` fails the page with a 500, and one that parses
460
+ (`{Tagline}`) renders as nothing. After the heading step, `update` writes a
461
+ `\` before every bare `{` on each mdx-body page — never in frontmatter, fenced
462
+ or inline code, a JSX tag or a `{/* … */}` comment — and keeps the page's
463
+ change only when your site's own MDX parser (the `@mdx-js/mdx` its
464
+ `next-mdx-remote` uses) then reads the page with no expression left but
465
+ comments. Each such page is listed under **Placeholder brace escaped**; one
466
+ byte per brace changes and nothing else. A page it cannot escape safely (a
467
+ mangled `{/ … /}` comment, or another MDX error the escape does not reach)
468
+ is left alone and listed under **Needs review** with the line, and so is
469
+ every page with a bare brace while the site's dependencies are not installed
470
+ (run `pnpm install`, then `update` again). A fresh scaffold also runs
471
+ `pnpm mdx-body:check` in `preflight` and CI, which names each bare brace by
472
+ file, line and column before a deploy. A site scaffolded before it gains the
473
+ check by copying `tools/check-mdx-body.ts`, `tools/mdx-braces.ts` and
474
+ `tools/body-heading.ts` from a fresh scaffold and adding
475
+ `"mdx-body:check": "tsx tools/check-mdx-body.ts"` to its `package.json`
476
+ scripts and `preflight`.
477
+
447
478
  ### Adopting an existing site
448
479
 
449
480
  A site that create-ailk did not scaffold, or that was scaffolded before
@@ -558,7 +589,9 @@ On a site with an Admin (the `workspace` or `multi-tenant` module), the
558
589
  starter's pages are also rows in the site's workspace, so the Admin's Docs
559
590
  lists Home, About, Contact and every other roster page on first open.
560
591
  `seed-workspace` writes them there: each `content/en/<Type>/<slug>.mdx` page
561
- becomes one draft `text/page` Artifact.
592
+ becomes one `text/page` Artifact, set Live when it has no Live version. A page
593
+ already Live is never published over: a newer draft stays a draft for the
594
+ owner to set Live in the Admin.
562
595
 
563
596
  ```bash
564
597
  AILK_AGENT_KEY=<agent key> create-ailk seed-workspace ./my-site --api-url <api origin> --workspace <wksp_id>
@@ -570,7 +603,9 @@ AILK_AGENT_KEY=<agent key> create-ailk seed-workspace ./my-site --api-url <api o
570
603
  and the origin's source, before any request.
571
604
  - The agent key is read from `AILK_AGENT_KEY` only — never a flag or a file —
572
605
  and never printed. An owner issues it in the Admin with the `content-edit`
573
- grant.
606
+ and `content-publish` grants. After the first refused publish, the rest of
607
+ the pages are saved as drafts and fail `not published`; a re-run with a
608
+ granted key publishes them.
574
609
  - Each page prints `created`, `updated`, `unchanged`, `skipped` or `failed`,
575
610
  then a count line. A re-run creates nothing and reports every page
576
611
  `unchanged`. An agent writes ten times a minute, so a first seed of about
@@ -580,6 +615,15 @@ AILK_AGENT_KEY=<agent key> create-ailk seed-workspace ./my-site --api-url <api o
580
615
  - The API must run with `content-write` and a tenancy module (`workspace` or
581
616
  `multi-tenant`) in `AILK_ENABLED_MODULES`. Without them the content routes
582
617
  are not served and every page fails `404`.
618
+ - When every page is in the workspace and Live, the site switches to the
619
+ database: `CONTENT_ADAPTER=database` is written to `apps/web/.env.local`
620
+ (which the web app reads) and to `apps/api/.env` (which the API that serves
621
+ the pages reads). Each app keeps a `CONTENT_ADAPTER` it already sets. A
622
+ skipped, refused or partial seed writes neither, and the site stays on
623
+ `content/` until a re-run succeeds.
624
+ - The API then serves the pages only when it runs on the database it was
625
+ seeded through (its `DATABASE_URL`) and its site resolves to the seeded
626
+ workspace.
583
627
  - Exit 0 when every page was seeded, 1 when any failed, 2 on a refusal (no
584
628
  `.ailk-site-manifest.json`, a spoke, a missing key, workspace id or origin,
585
629
  the adapter not installed), which sends nothing.
@@ -610,6 +654,22 @@ scaffold time — a site with no `.ailk-site-manifest.json`, or a spoke — into
610
654
  a workspace: each `content/en/<Type>/<slug>.mdx` page, and each legacy
611
655
  `content/<Type>/<slug>.mdx` page, becomes one draft `text/page` Artifact.
612
656
 
657
+ When the site has the docs-blog tree `apps/web/content`, its posts and docs
658
+ are imported too (#10701): `apps/web/content/en/blog/<slug>.mdx` and
659
+ `blog/<slug>/index.mdx` become `BlogPosting/<slug>`, or one series down,
660
+ `blog/<series>/<post>.mdx` (or `<post>/index.mdx`), as a `BlogPosting/<post>`
661
+ whose `parent` is the series (#10775) — served at `/blog/<series>/<post>`, the
662
+ URL its file serves at; the same under `docs` become `DocsDetail/<slug>`, and
663
+ each directory's own `index.mdx` becomes its `BlogList/index` or
664
+ `DocsList/index` — replacing a `content/` teaser of that type. A blog post's
665
+ `parent` comes from its path, never its frontmatter, and a file the walk skips
666
+ is listed with the rule it failed. Their fumadocs frontmatter is mapped to
667
+ the stored fields (`date` →
668
+ `publishedAt`, `updated` → `updatedAt`); keys the store does not keep from a
669
+ caller (`featured`, `sidebar`, `tags`) are not sent, and `--apply` names them
670
+ in one line per file. Each outcome carries `root: "content"` or
671
+ `root: "editorial"`, and an editorial line is printed prefixed `editorial: `.
672
+
613
673
  ```bash
614
674
  AILK_AGENT_KEY=<agent key> create-ailk migrate-content ./my-site --api-url <api origin> --workspace <wksp_id>
615
675
  AILK_AGENT_KEY=<agent key> create-ailk migrate-content ./my-site --apply --api-url <api origin> --workspace <wksp_id>
@@ -669,6 +729,50 @@ diff -r before after && echo "live site unchanged"
669
729
  A non-empty diff is a finding: stop and report it before migrating another
670
730
  site.
671
731
 
732
+ ### Cutting a site over to the database
733
+
734
+ Once a site's pages are in its workspace, three commands move it from its
735
+ page files to the database and back (#10648). Each reads the workspace with
736
+ `AILK_AGENT_KEY` (the `content-edit` grant), `--api-url`/`AILK_API_URL` and
737
+ `--workspace`/`AILK_WORKSPACE_ID`, as `migrate-content` does; a spoke also
738
+ needs `AILK_SPOKE_KEY`.
739
+
740
+ ```bash
741
+ # 1. Freeze what the site serves today, and audit it. Commit both files.
742
+ create-ailk inventory . --origin https://example.com
743
+ # → .ailk-cutover/inventory.json (written once; --refreeze crawls again)
744
+ # → .ailk-cutover/audit.json (every run: per page, words in Markdown? published?)
745
+
746
+ # 2. Plan the cutover, then run it against a preview reading the database.
747
+ create-ailk cutover . --before https://example.com --after https://preview.example.com
748
+ create-ailk cutover . --before https://example.com --after https://preview.example.com --apply
749
+
750
+ # Rollback: every published page back to its file, published edits kept.
751
+ create-ailk export-content .
752
+ ```
753
+
754
+ `inventory` freezes every URL the sitemap, a crawl and the route roster name,
755
+ with each page's prose status and images, and joins each page to its
756
+ workspace page — by the inventory's operator-written `joins`, the roster,
757
+ then `/blog` and the docs path. It exits 1 when any prose page is not
758
+ published or its words are not in its Markdown.
759
+
760
+ `cutover` runs import → publish → inventory → comparison → set env → deploy →
761
+ verify → remove files, in that order, and stops at the first step that fails.
762
+ The comparison renders every frozen page on both origins at 1440 and 390 wide
763
+ and records a text, JSON-LD and screenshot verdict for each, then sweeps every
764
+ frozen URL for a direct 200; it needs Playwright's Chromium in the site
765
+ (`pnpm add -D @playwright/test && pnpm exec playwright install chromium`).
766
+ Without `--apply` it sends only reads and writes and spawns nothing. The kit
767
+ never publishes: the publish step names the pages to publish in Admin. Step 8
768
+ stages the imported files' removal with `git rm`; you commit it. Each run's
769
+ record and screenshots go to `.ailk-cutover/runs/` (gitignored).
770
+
771
+ `export-content` writes each published page to the file `migrate-content`
772
+ reads it from, with its words moved back into its sections, so a re-run of
773
+ `migrate-content` reports every page `unchanged`. The full sequence, the
774
+ evidence each step leaves and each step's rollback are in the cutover runbook.
775
+
672
776
  ### A spoke's declared environment
673
777
 
674
778
  A spoke scaffold's `pnpm preflight` and CI run `pnpm env:declared`: every
@@ -755,7 +859,7 @@ The network invariant (#6759 D6), in full:
755
859
  3. Substitutes `workspace:*` references in every `package.json` with the matching `@working-theory/*` semver from npm — versions are pinned at npm-publish time.
756
860
  4. Substitutes `{{PROJECT_NAME}}`, `{{COMPANY_NAME}}`, `{{COMPANY_DOMAIN}}` markers in `templates/README.md` from the target directory name (and CLI flags / interactive prompts in future versions).
757
861
  5. Derives and writes `architecture.yaml` (the site's code-boundary config) from the selected module set — see below.
758
- 6. On a site with an Admin, seeds the starter's pages into its workspace when `--install` ran and `AILK_API_URL`, `AILK_WORKSPACE_ID` and `AILK_AGENT_KEY` are set, else prints why not and the `seed-workspace` command — see [Seeding the workspace](#seeding-the-workspace).
862
+ 6. On a site with an Admin, seeds the starter's pages into its workspace when `--install` ran and `AILK_API_URL`, `AILK_WORKSPACE_ID` and `AILK_AGENT_KEY` are set, sets each page Live and, when every page is Live, switches `apps/web` and `apps/api` to `CONTENT_ADAPTER=database`; else prints why not and the `seed-workspace` command that seeds and switches later — see [Seeding the workspace](#seeding-the-workspace).
759
863
  7. Prints next steps: `cd <dir> && pnpm install && pnpm dev`.
760
864
 
761
865
  ## `architecture.yaml` is derived from the module set